Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 38 additions & 72 deletions agent/agency-report
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@ of expandable blocks is variable (0, 1, 2, N β€” your call):
[Yes] [More]
[Skip]

Blocks are specified one of two ways:
β€’ Legacy 2-block: --draft (becomes "πŸ“ Drafted action") and --reasoning
(becomes "πŸ“Ž Context"). Backward-compatible with the original PR.
β€’ Flexible: --block (repeatable, JSON object). Each --block becomes
one expandable. Use this when you have a different shape β€” e.g.
three variants A/B/C, or a single "context" block, or zero blocks
(--info-only with just headline + source link).
Blocks are specified via --block (repeatable, JSON object). Each --block
becomes one expandable. Pass any number for 0/1/2/N expandables β€” typical
copilot card is two (option A / option B). If no --block is passed and a
--prompt is set, a single auto-generated draft block is created so the
user can see what'll run on Yes-tap.

Yes-tap routing β€” one goal, one topic:
β€’ Yes/More dispatch in the topic the card lives in. That topic is the
Expand Down Expand Up @@ -87,16 +85,10 @@ Field mapping:
--image-text alt: shorthand text β†’ auto-generated local card image
--block repeatable; each value is a JSON object
{emoji, title, body[, body_html]} β†’ one expandable.
When given, fully overrides --draft / --reasoning.
Use for 3-variant pickers, info-only cards, or any
non-2-block shape.
--draft legacy: actionable content (first expandable).
Falls back to --prompt when omitted.
--reasoning context block (second expandable, "πŸ“Ž Context").
Falls back to --description when omitted.
--description legacy alias for --reasoning (kept for back-compat)
--prompt exact action that runs if user taps Yes β€” also fills
draft when --draft is not given.
Two blocks for option A/B is the typical copilot
card; single block for status confirms.
--prompt exact action that runs if user taps Yes; also
auto-generates a draft block when no --block given.
--importance high|med|low (default med)
--source stable slug for dedupe
--skip-if-exists suppress posting if the source slug already has a
Expand All @@ -106,8 +98,10 @@ Field mapping:
--info-only omit the inline keyboard entirely (FYI cards).
--thread-id forum topic to post into (defaults to $TG_THREAD_ID)

Inputs are HTML-escaped by default. To embed raw HTML in a field use the
`--<field>-html` long form (e.g. `--draft-html '<code>...</code>'`).
Inputs are HTML-escaped by default. To embed raw HTML in --block bodies,
pass `"body_html": true` alongside `"body": "<your html>"`. The
`--title-html`, `--subhead-html`, `--source-label-html` long forms remain
for raw HTML in headline / subhead / source label.
"""
from __future__ import annotations

Expand Down Expand Up @@ -353,18 +347,13 @@ def _build_keyboard(
def _resolve_blocks(args: argparse.Namespace) -> list[dict]:
"""Build the ordered list of expandable blocks rendered in the card.

Two ways to specify them:
β€’ --block JSON (repeatable): full control. Each value is a JSON
object {"emoji": "πŸ“", "title": "Variant A", "body": "..."}.
When ANY --block is passed, the legacy --draft / --reasoning
flags are ignored β€” --block fully overrides them.
β€’ Legacy --draft / --reasoning (each renders one block): the
original 2-block shape, useful for the common case.
Pass --block JSON (repeatable). Each value is a JSON object:
{"emoji": "πŸ“", "title": "Variant A", "body": "..."}
or with raw HTML: {..., "body": "<b>X</b>", "body_html": true}.

Body content is HTML-passed-through when --block is used; the caller
is responsible for escaping (use the helper to wrap raw text). For
--draft / --reasoning, the existing escape rules apply (raw HTML via
--draft-html / --reasoning-html, otherwise auto-escaped).
If no --block is passed and --prompt is set, a single block with the
prompt as the body is auto-generated so the user can see what'll run
on Yes-tap.

Returns a list of {emoji, title, body_html} dicts.
"""
Expand All @@ -388,26 +377,16 @@ def _resolve_blocks(args: argparse.Namespace) -> list[dict]:
})
return out

blocks: list[dict] = []
draft = _resolve_field(args.draft, args.draft_html)
if not draft and args.prompt:
draft = f"<pre>{html.escape(args.prompt, quote=False)}</pre>"
if draft:
blocks.append({
"emoji": args.draft_emoji or "πŸ“",
"title": args.draft_title or "Drafted action",
"body_html": draft,
})
reasoning = _resolve_field(args.reasoning, args.reasoning_html)
if not reasoning and args.description:
reasoning = _esc(args.description)
if reasoning:
blocks.append({
"emoji": args.reasoning_emoji or "πŸ“Ž",
"title": args.reasoning_title or "Context",
"body_html": reasoning,
})
return blocks
# No --block: auto-generate one from --prompt so the user always sees
# what'll fire on Yes-tap. Cards with no prompt + no block + no
# info-only flag are caught by the validation in main().
if (args.prompt or "").strip():
return [{
"emoji": "πŸ“",
"title": "Drafted action",
"body_html": f"<pre>{html.escape(args.prompt, quote=False)}</pre>",
}]
return []


def _build_body(args: argparse.Namespace) -> str:
Expand Down Expand Up @@ -605,33 +584,14 @@ def main() -> int:
default=None,
help='Repeatable expandable block as JSON: '
'\'{"emoji":"πŸ“","title":"Variant A","body":"..."}\'. '
"When given, overrides --draft / --reasoning. Pass any number "
"of --block flags for 0/1/2/N expandables.",
"Pass any number of --block flags for 0/1/2/N expandables.",
)
p.add_argument("--draft", help="Actionable content (first expandable).")
p.add_argument("--draft-title", default="Drafted action", help="Title of the draft block.")
p.add_argument("--draft-emoji", default="πŸ“", help="Emoji prefix on the draft block.")
p.add_argument(
"--reasoning",
help="Context block (second expandable). Provenance, why-now, "
"related threads, anything supporting the decision but not "
"the action itself.",
)
p.add_argument("--reasoning-title", default="Context", help="Title of the context block.")
p.add_argument("--reasoning-emoji", default="πŸ“Ž", help="Emoji prefix on the context block.")
p.add_argument("--title-html", help="Raw HTML for the headline (skips escaping).")
p.add_argument("--source-label-html", help="Raw HTML for the source label.")
p.add_argument("--subhead-html", help="Raw HTML for the subhead.")
p.add_argument("--draft-html", help="Raw HTML for the draft block body.")
p.add_argument("--reasoning-html", help="Raw HTML for the why block body.")
p.add_argument(
"--description",
default="",
help="Legacy alias: used as reasoning if --reasoning is not given.",
)
p.add_argument(
"--prompt",
help="Exact action that runs if user taps Yes β€” also fills draft when --draft is not given.",
help="Exact action that runs if user taps Yes β€” also fills the auto-generated draft block when no --block is given.",
)
p.add_argument(
"--importance",
Expand Down Expand Up @@ -745,10 +705,16 @@ def main() -> int:
)
return 0

# DB description was historically the legacy --reasoning text. After v7
# the blocks fully replace it; we store the concatenated block bodies so
# search / dedup still see something searchable.
db_description = "\n\n".join(
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Store plain text in description instead of rendered body_html; current code persists HTML markup that pollutes refine/more context and displays escaped tags.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At agent/agency-report, line 711:

<comment>Store plain text in `description` instead of rendered `body_html`; current code persists HTML markup that pollutes refine/more context and displays escaped tags.</comment>

<file context>
@@ -745,10 +705,16 @@ def main() -> int:
+    # DB description was historically the legacy --reasoning text. After v7
+    # the blocks fully replace it; we store the concatenated block bodies so
+    # search / dedup still see something searchable.
+    db_description = "\n\n".join(
+        (b.get("body_html") or "").strip() for b in blocks
+    ).strip() or (args.prompt or "")
</file context>
Fix with Cubic

(b.get("body_html") or "").strip() for b in blocks
).strip() or (args.prompt or "")
sugg_id = agency_db.insert(
db,
title=args.title,
description=args.description or args.reasoning or "",
description=db_description,
importance=args.importance,
source=args.source,
prompt=args.prompt,
Expand Down
19 changes: 19 additions & 0 deletions agent/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ if command -v npm >/dev/null 2>&1 && ! sudo -iu bux command -v codex >/dev/null
|| echo "bootstrap: codex install failed (non-fatal β€” /codex login will hint how to install later)" >&2
fi

# Enable Codex /goal autopilot feature: `[features] goals = true` in
# ~/.codex/config.toml. Idempotent β€” leaves existing config alone if
# goals=true or a [features] block is already present.
sudo -u bux -H bash -c '
CODEX_CONFIG="$HOME/.codex/config.toml"
mkdir -p "$(dirname "$CODEX_CONFIG")"
if [ ! -f "$CODEX_CONFIG" ]; then
printf "[features]\ngoals = true\n" > "$CODEX_CONFIG"
elif ! grep -qE "^[[:space:]]*goals[[:space:]]*=" "$CODEX_CONFIG"; then
if grep -qE "^[[:space:]]*\[features\]" "$CODEX_CONFIG"; then
echo "bootstrap: warn β€” existing [features] block in $CODEX_CONFIG; add goals = true manually" >&2
else
printf "\n[features]\ngoals = true\n" >> "$CODEX_CONFIG"
fi
fi
chmod 0644 "$CODEX_CONFIG"
' || echo "bootstrap: codex config write failed (non-fatal)" >&2

# --- agent shell helpers --------------------------------------------------
# install.sh creates these symlinks on first boot, but new helpers added to
# agent/ after a box has already been provisioned never get linked into
Expand All @@ -95,6 +113,7 @@ ln -sfn "$REPO_DIR/agent/tg-send" /usr/local/bin/tg-send
ln -sfn "$REPO_DIR/agent/tg-buttons" /usr/local/bin/tg-buttons
ln -sfn "$REPO_DIR/agent/tg-schedule" /usr/local/bin/tg-schedule
ln -sfn "$REPO_DIR/agent/tg-schedule-fire" /usr/local/bin/tg-schedule-fire
ln -sfn /usr/local/bin/tg-schedule /usr/local/bin/schedule
ln -sfn "$REPO_DIR/agent/agency-report" /usr/local/bin/agency-report
ln -sfn "$REPO_DIR/agent/bux-restart" /usr/local/bin/bux-restart
ln -sfn "$REPO_DIR/agent/bux-miniapp-tunnel" /usr/local/bin/bux-miniapp-tunnel
Expand Down
Loading
Loading