agent-relay-server 0.104.5 → 0.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/openapi.json +141 -1
- package/package.json +2 -2
- package/public/assets/MessageBubble-CHor4Sxp.js +4 -0
- package/public/assets/{MessageBubble-BpiTbbnp.js.map → MessageBubble-CHor4Sxp.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-k8Aa98g5.js → PlanGraphPanel-jHfkQBX6.js} +2 -2
- package/public/assets/{PlanGraphPanel-k8Aa98g5.js.map → PlanGraphPanel-jHfkQBX6.js.map} +1 -1
- package/public/assets/{activity-CxKi5Erb.js → activity-DDXS6LsX.js} +2 -2
- package/public/assets/{activity-CxKi5Erb.js.map → activity-DDXS6LsX.js.map} +1 -1
- package/public/assets/{agent-profiles-DMZXdN6K.js → agent-profiles-Cn8lCTkg.js} +2 -2
- package/public/assets/{agent-profiles-DMZXdN6K.js.map → agent-profiles-Cn8lCTkg.js.map} +1 -1
- package/public/assets/agents-DBTdcFFz.js +2 -0
- package/public/assets/{agents-BrvpH3gn.js.map → agents-DBTdcFFz.js.map} +1 -1
- package/public/assets/{analytics-1fvh-9ok.js → analytics-VximqXQ9.js} +3 -3
- package/public/assets/{analytics-1fvh-9ok.js.map → analytics-VximqXQ9.js.map} +1 -1
- package/public/assets/{automation-BCgX5WOH.js → automation-L9wq4y5F.js} +2 -2
- package/public/assets/{automation-BCgX5WOH.js.map → automation-L9wq4y5F.js.map} +1 -1
- package/public/assets/{badge-BLar6FjP.js → badge-D8fABzMs.js} +2 -2
- package/public/assets/{badge-BLar6FjP.js.map → badge-D8fABzMs.js.map} +1 -1
- package/public/assets/{branch-state-badge-DYA7bU0i.js → branch-state-badge-CcLZ_u86.js} +2 -2
- package/public/assets/{branch-state-badge-DYA7bU0i.js.map → branch-state-badge-CcLZ_u86.js.map} +1 -1
- package/public/assets/{button-BrIB69qd.js → button-KASZcscA.js} +2 -2
- package/public/assets/{button-BrIB69qd.js.map → button-KASZcscA.js.map} +1 -1
- package/public/assets/card-Cds0MWME.js +2 -0
- package/public/assets/{card-lhnFnBoz.js.map → card-Cds0MWME.js.map} +1 -1
- package/public/assets/{channels-CK80oJYG.js → channels-CcSnLzCG.js} +2 -2
- package/public/assets/{channels-CK80oJYG.js.map → channels-CcSnLzCG.js.map} +1 -1
- package/public/assets/{chat-D_2PEz06.js → chat-BzMj_PO6.js} +2 -2
- package/public/assets/{chat-D_2PEz06.js.map → chat-BzMj_PO6.js.map} +1 -1
- package/public/assets/{connectors-ChGyht5m.js → connectors-DbPOlQhL.js} +2 -2
- package/public/assets/{connectors-ChGyht5m.js.map → connectors-DbPOlQhL.js.map} +1 -1
- package/public/assets/copy-button-d9DFNX9v.js +2 -0
- package/public/assets/{copy-button-Cd-LbSbg.js.map → copy-button-d9DFNX9v.js.map} +1 -1
- package/public/assets/{display-D1xB8NJZ.js → display-BWi8DQCp.js} +2 -2
- package/public/assets/display-BWi8DQCp.js.map +1 -0
- package/public/assets/{dist-BnbGIUmi.js → dist-BbUJ1Vht.js} +2 -2
- package/public/assets/{dist-BnbGIUmi.js.map → dist-BbUJ1Vht.js.map} +1 -1
- package/public/assets/{dist-Bp10H3oR.js → dist-BlZvUZZB.js} +2 -2
- package/public/assets/{dist-Bp10H3oR.js.map → dist-BlZvUZZB.js.map} +1 -1
- package/public/assets/{dist-DDUGmmoi.js → dist-BwwlmyBq.js} +2 -2
- package/public/assets/{dist-DDUGmmoi.js.map → dist-BwwlmyBq.js.map} +1 -1
- package/public/assets/dist-CBVZk9U8.js +2 -0
- package/public/assets/{dist-BEzpMqLy.js.map → dist-CBVZk9U8.js.map} +1 -1
- package/public/assets/{dist-B5VvlkhI.js → dist-CgqfdHSt.js} +2 -2
- package/public/assets/{dist-B5VvlkhI.js.map → dist-CgqfdHSt.js.map} +1 -1
- package/public/assets/dist-DC-TqTz2.js +2 -0
- package/public/assets/{dist-30weaXto.js.map → dist-DC-TqTz2.js.map} +1 -1
- package/public/assets/{dist-DZ97OosY.js → dist-DOAjFOS4.js} +2 -2
- package/public/assets/{dist-DZ97OosY.js.map → dist-DOAjFOS4.js.map} +1 -1
- package/public/assets/{dist-BjOBkHHY.js → dist-DTXKbk2K.js} +2 -2
- package/public/assets/{dist-BjOBkHHY.js.map → dist-DTXKbk2K.js.map} +1 -1
- package/public/assets/dist-DreO5AcZ.js +2 -0
- package/public/assets/{dist-Cgy0FmP-.js.map → dist-DreO5AcZ.js.map} +1 -1
- package/public/assets/{dist-CQF3Jiqg.js → dist-DybScJmm.js} +2 -2
- package/public/assets/{dist-CQF3Jiqg.js.map → dist-DybScJmm.js.map} +1 -1
- package/public/assets/{dist-DAl28ejX.js → dist-UhEAFdvZ.js} +2 -2
- package/public/assets/{dist-DAl28ejX.js.map → dist-UhEAFdvZ.js.map} +1 -1
- package/public/assets/{es2015-B8Z9p8oQ.js → es2015-DUjFyjDp.js} +2 -2
- package/public/assets/{es2015-B8Z9p8oQ.js.map → es2015-DUjFyjDp.js.map} +1 -1
- package/public/assets/{formatted-body-impl-BJ4WCF5J.js → formatted-body-impl-CFWHLkCY.js} +2 -2
- package/public/assets/{formatted-body-impl-BJ4WCF5J.js.map → formatted-body-impl-CFWHLkCY.js.map} +1 -1
- package/public/assets/index-BjjzI2LT.css +2 -0
- package/public/assets/{index-CN7QZlMb.js → index-aCfZQ7LY.js} +13 -13
- package/public/assets/index-aCfZQ7LY.js.map +1 -0
- package/public/assets/{input-B_fUHtLD.js → input-CtHfLn1i.js} +2 -2
- package/public/assets/{input-B_fUHtLD.js.map → input-CtHfLn1i.js.map} +1 -1
- package/public/assets/{insights-BCZRV0hX.js → insights-BuPFE0tf.js} +2 -2
- package/public/assets/{insights-BCZRV0hX.js.map → insights-BuPFE0tf.js.map} +1 -1
- package/public/assets/{integrations-C7UGs00U.js → integrations-hkSW8CzM.js} +2 -2
- package/public/assets/{integrations-C7UGs00U.js.map → integrations-hkSW8CzM.js.map} +1 -1
- package/public/assets/{lib-ng6WZYjE.js → lib-l-JkMVR9.js} +2 -2
- package/public/assets/{lib-ng6WZYjE.js.map → lib-l-JkMVR9.js.map} +1 -1
- package/public/assets/{lucide-react-BYZuAhQs.js → lucide-react-C4eXYCwx.js} +2 -2
- package/public/assets/{lucide-react-BYZuAhQs.js.map → lucide-react-C4eXYCwx.js.map} +1 -1
- package/public/assets/{maintenance-ChYno3Fm.js → maintenance-wYwJ7w6h.js} +2 -2
- package/public/assets/{maintenance-ChYno3Fm.js.map → maintenance-wYwJ7w6h.js.map} +1 -1
- package/public/assets/{managed-agents-DjxZQNIR.js → managed-agents-CKaYD93I.js} +4 -4
- package/public/assets/{managed-agents-DjxZQNIR.js.map → managed-agents-CKaYD93I.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DFa5HF1H.js → markdown-preview-impl-bf4eDQUM.js} +2 -2
- package/public/assets/{markdown-preview-impl-DFa5HF1H.js.map → markdown-preview-impl-bf4eDQUM.js.map} +1 -1
- package/public/assets/memory-DIneEza9.js +2 -0
- package/public/assets/{memory-CTw6kadT.js.map → memory-DIneEza9.js.map} +1 -1
- package/public/assets/{messages-Cjr816D_.js → messages-DP9Vv_wh.js} +2 -2
- package/public/assets/{messages-Cjr816D_.js.map → messages-DP9Vv_wh.js.map} +1 -1
- package/public/assets/{orchestrators-CdmFKA7K.js → orchestrators-CAnKjxdO.js} +2 -2
- package/public/assets/{orchestrators-CdmFKA7K.js.map → orchestrators-CAnKjxdO.js.map} +1 -1
- package/public/assets/{overview-DBOnjfpP.js → overview-B98U3C-y.js} +2 -2
- package/public/assets/{overview-DBOnjfpP.js.map → overview-B98U3C-y.js.map} +1 -1
- package/public/assets/{pairs-BkcdKdF7.js → pairs-DVeeuxhY.js} +2 -2
- package/public/assets/{pairs-BkcdKdF7.js.map → pairs-DVeeuxhY.js.map} +1 -1
- package/public/assets/{projects-ClKbDQB1.js → projects-vt63bwHN.js} +2 -2
- package/public/assets/{projects-ClKbDQB1.js.map → projects-vt63bwHN.js.map} +1 -1
- package/public/assets/prompt-settings-Df-0ZMJ5.js +2 -0
- package/public/assets/prompt-settings-Df-0ZMJ5.js.map +1 -0
- package/public/assets/{react-dom-Dbhs84ug.js → react-dom-BMF4hNzG.js} +2 -2
- package/public/assets/{react-dom-Dbhs84ug.js.map → react-dom-BMF4hNzG.js.map} +1 -1
- package/public/assets/{registry-Bsf3RtOO.js → registry-DlLZyW7_.js} +2 -2
- package/public/assets/{registry-Bsf3RtOO.js.map → registry-DlLZyW7_.js.map} +1 -1
- package/public/assets/{security-Iyu5bhJw.js → security-Bo4Ty3lz.js} +2 -2
- package/public/assets/{security-Iyu5bhJw.js.map → security-Bo4Ty3lz.js.map} +1 -1
- package/public/assets/{select-Bi6tNJDx.js → select-BguZ80M8.js} +2 -2
- package/public/assets/{select-Bi6tNJDx.js.map → select-BguZ80M8.js.map} +1 -1
- package/public/assets/settings-CpX9Xw2j.js +6 -0
- package/public/assets/{settings-CbHp2F9r.js.map → settings-CpX9Xw2j.js.map} +1 -1
- package/public/assets/{store-zeyDiSKE.js → store-DUfrGvOE.js} +3 -3
- package/public/assets/{store-zeyDiSKE.js.map → store-DUfrGvOE.js.map} +1 -1
- package/public/assets/{tasks-CPlf-5Tr.js → tasks-Cg1HXY9l.js} +2 -2
- package/public/assets/{tasks-CPlf-5Tr.js.map → tasks-Cg1HXY9l.js.map} +1 -1
- package/public/assets/{teams-D2uUNhlY.js → teams-BFcYunxB.js} +2 -2
- package/public/assets/{teams-D2uUNhlY.js.map → teams-BFcYunxB.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-uRzbrlXy.js → terminal-viewer-impl-CF2EBa2K.js} +3 -3
- package/public/assets/{terminal-viewer-impl-uRzbrlXy.js.map → terminal-viewer-impl-CF2EBa2K.js.map} +1 -1
- package/public/assets/types-DorDDvEb.js.map +1 -1
- package/public/assets/{use-keyboard-viewport-D8_NYDkh.js → use-keyboard-viewport-RYMALvu1.js} +2 -2
- package/public/assets/{use-keyboard-viewport-D8_NYDkh.js.map → use-keyboard-viewport-RYMALvu1.js.map} +1 -1
- package/public/assets/user-avatar-D1ffSClz.js +2 -0
- package/public/assets/{user-avatar-GFWMuAGK.js.map → user-avatar-D1ffSClz.js.map} +1 -1
- package/public/assets/{work-queue-B30SNmuq.js → work-queue-DV36cxjp.js} +2 -2
- package/public/assets/{work-queue-B30SNmuq.js.map → work-queue-DV36cxjp.js.map} +1 -1
- package/public/assets/workspaces-CIsQqQby.js +3 -0
- package/public/assets/workspaces-CIsQqQby.js.map +1 -0
- package/public/index.html +22 -22
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +114 -23
- package/runner/plugins/claude/monitors/relay-monitor.ts +2 -2
- package/runner/src/adapter.ts +4 -1
- package/src/agent-lifecycle-events.ts +21 -5
- package/src/automations/reconcile.ts +4 -1
- package/src/branch-landed.ts +22 -4
- package/src/config-store.ts +17 -21
- package/src/context-advisory.ts +10 -3
- package/src/db/index.ts +1 -0
- package/src/db/projects.ts +29 -0
- package/src/db/prompt-templates-schema.ts +20 -0
- package/src/db/prompt-templates.ts +91 -0
- package/src/db/schema.ts +5 -4
- package/src/db/tasks.ts +2 -1
- package/src/db/workspaces.ts +41 -1
- package/src/index.ts +3 -0
- package/src/project-instructions.ts +7 -14
- package/src/prompt-resolver.ts +530 -0
- package/src/prompt-template-defaults-runner.ts +136 -0
- package/src/routes/index.ts +5 -0
- package/src/routes/prompt-templates.ts +60 -0
- package/src/routes/workspaces.ts +4 -3
- package/src/routing-policy-push.ts +4 -1
- package/src/security.ts +3 -0
- package/src/self-resume.ts +8 -15
- package/src/services/prompt-templates.ts +19 -0
- package/src/services/register-agent.ts +2 -0
- package/src/services/spawn-issue-activity.ts +107 -0
- package/src/spawn-targets.ts +3 -11
- package/src/steward.ts +5 -23
- package/src/workspace-auto-merge.ts +19 -2
- package/src/workspace-phase.ts +15 -28
- package/public/assets/MessageBubble-BpiTbbnp.js +0 -4
- package/public/assets/agents-BrvpH3gn.js +0 -2
- package/public/assets/card-lhnFnBoz.js +0 -2
- package/public/assets/copy-button-Cd-LbSbg.js +0 -2
- package/public/assets/display-D1xB8NJZ.js.map +0 -1
- package/public/assets/dist-30weaXto.js +0 -2
- package/public/assets/dist-BEzpMqLy.js +0 -2
- package/public/assets/dist-Cgy0FmP-.js +0 -2
- package/public/assets/index-BMO_Sblt.css +0 -2
- package/public/assets/index-CN7QZlMb.js.map +0 -1
- package/public/assets/memory-CTw6kadT.js +0 -2
- package/public/assets/settings-CbHp2F9r.js +0 -6
- package/public/assets/user-avatar-GFWMuAGK.js +0 -2
- package/public/assets/workspaces-CSpmgbPT.js +0 -3
- package/public/assets/workspaces-CSpmgbPT.js.map +0 -1
|
@@ -847,13 +847,109 @@ function messageBodyMaxCharsFromEnv() {
|
|
|
847
847
|
return process.env.AGENT_RELAY_MESSAGE_BODY_MAX_CHARS;
|
|
848
848
|
}
|
|
849
849
|
|
|
850
|
-
//
|
|
851
|
-
|
|
852
|
-
|
|
850
|
+
// src/prompt-template-defaults-runner.ts
|
|
851
|
+
function lines(...parts) {
|
|
852
|
+
return parts.join(`
|
|
853
|
+
`);
|
|
854
|
+
}
|
|
855
|
+
var RUNNER_PROMPT_TEMPLATE_DEFAULTS = [
|
|
856
|
+
{
|
|
857
|
+
slug: "relay.manual.claude",
|
|
858
|
+
category: "runner",
|
|
859
|
+
title: "Claude Relay Manual",
|
|
860
|
+
description: "Bundled CLAUDE.md relay manual written into provisioned Claude homes.",
|
|
861
|
+
template: lines("# Agent Relay", "", "- Agent Relay messages may come from humans, channels, or other agents.", "- Read the delivered message body and do the requested work.", "- Reply through Relay only when the sender still needs an answer.", "- Prefer the inline MCP tools `relay_reply` or `relay_send_message` for the actual answer to an incoming Relay message.", "- Do not send `/message` with the useful answer and then a separate `/reply` that only says it was sent.", "- If multiple Relay messages arrive together, answer once to the latest relevant message and cover the current request. Do not separately acknowledge stale greetings or context.", '- If the useful response was already delivered through Relay, do not send an extra "sent", "done", or "drafts sent" confirmation unless the user explicitly asked for one.', "- No reply is needed for pure info messages, passive acknowledgements, or reactions that do not ask for action.", "- NEVER reply to a notification-class message. The server marks these and renders a single `\u21AA Notification \u2014 no reply needed.` line instead of the reply reminder \u2014 it is a fire-and-forget signal (a merge notice, lifecycle event, or FYI). Act on the information if relevant, but do not send any reply, status confirmation, or reaction back.", '- Use `agent-relay /react <messageId> <emoji>` instead of a text reply for lightweight acknowledgement, approval, thanks, or "good job" after a completed work update.', "- Good reaction uses: acknowledge praise with \uD83D\uDC4D or \u2764\uFE0F, mark a completed handoff as seen, approve a proposed next step, or acknowledge a passive FYI.", "- Do not use reactions when the user asked a question, gave a new task, reported a bug, or needs a textual result.", "- A thumbs-up reaction to your question means approval to proceed. A thumbs-up reaction to your completed status means no further action.", "- Use `agent-relay get-message <id>` when a delivered preview is truncated.", "- Use `agent-relay /status --json` to inspect your current Relay identity and `agent-relay /guide` for command details."),
|
|
862
|
+
defaultTemplate: lines("# Agent Relay", "", "- Agent Relay messages may come from humans, channels, or other agents.", "- Read the delivered message body and do the requested work.", "- Reply through Relay only when the sender still needs an answer.", "- Prefer the inline MCP tools `relay_reply` or `relay_send_message` for the actual answer to an incoming Relay message.", "- Do not send `/message` with the useful answer and then a separate `/reply` that only says it was sent.", "- If multiple Relay messages arrive together, answer once to the latest relevant message and cover the current request. Do not separately acknowledge stale greetings or context.", '- If the useful response was already delivered through Relay, do not send an extra "sent", "done", or "drafts sent" confirmation unless the user explicitly asked for one.', "- No reply is needed for pure info messages, passive acknowledgements, or reactions that do not ask for action.", "- NEVER reply to a notification-class message. The server marks these and renders a single `\u21AA Notification \u2014 no reply needed.` line instead of the reply reminder \u2014 it is a fire-and-forget signal (a merge notice, lifecycle event, or FYI). Act on the information if relevant, but do not send any reply, status confirmation, or reaction back.", '- Use `agent-relay /react <messageId> <emoji>` instead of a text reply for lightweight acknowledgement, approval, thanks, or "good job" after a completed work update.', "- Good reaction uses: acknowledge praise with \uD83D\uDC4D or \u2764\uFE0F, mark a completed handoff as seen, approve a proposed next step, or acknowledge a passive FYI.", "- Do not use reactions when the user asked a question, gave a new task, reported a bug, or needs a textual result.", "- A thumbs-up reaction to your question means approval to proceed. A thumbs-up reaction to your completed status means no further action.", "- Use `agent-relay get-message <id>` when a delivered preview is truncated.", "- Use `agent-relay /status --json` to inspect your current Relay identity and `agent-relay /guide` for command details."),
|
|
863
|
+
variables: []
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
slug: "relay.context.claude",
|
|
867
|
+
category: "runner",
|
|
868
|
+
title: "Claude Relay Context",
|
|
869
|
+
description: "One-line relay context note for Claude sessions.",
|
|
870
|
+
template: "[agent-relay] Relay is available. Follow the Agent Relay rules in CLAUDE.md when present, or run agent-relay /guide. Reply through Relay only when a response is needed; do not send status-only follow-ups after the useful Relay response was already delivered.",
|
|
871
|
+
defaultTemplate: "[agent-relay] Relay is available. Follow the Agent Relay rules in CLAUDE.md when present, or run agent-relay /guide. Reply through Relay only when a response is needed; do not send status-only follow-ups after the useful Relay response was already delivered.",
|
|
872
|
+
variables: []
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
slug: "relay.context.claude.readonly",
|
|
876
|
+
category: "runner",
|
|
877
|
+
title: "Claude Relay Context (Read Only)",
|
|
878
|
+
description: "Read-only relay context note for Claude sessions.",
|
|
879
|
+
template: lines("[agent-relay] Relay is available. Follow the Agent Relay rules in CLAUDE.md when present, or run agent-relay /guide. Reply through Relay only when a response is needed; do not send status-only follow-ups after the useful Relay response was already delivered.", "", "This Claude session is running with restricted read-only Relay permissions. Do not invoke Agent Relay skills. If you need to reply, use this Bash command shape:", "", 'agent-relay /reply <messageId> "<your reply>"'),
|
|
880
|
+
defaultTemplate: lines("[agent-relay] Relay is available. Follow the Agent Relay rules in CLAUDE.md when present, or run agent-relay /guide. Reply through Relay only when a response is needed; do not send status-only follow-ups after the useful Relay response was already delivered.", "", "This Claude session is running with restricted read-only Relay permissions. Do not invoke Agent Relay skills. If you need to reply, use this Bash command shape:", "", 'agent-relay /reply <messageId> "<your reply>"'),
|
|
881
|
+
variables: []
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
slug: "relay.context.codex",
|
|
885
|
+
category: "runner",
|
|
886
|
+
title: "Codex Relay Context",
|
|
887
|
+
description: "Relay context note injected into Codex sessions.",
|
|
888
|
+
template: "[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id \u2014 relay fills it from your token; use relay_whoami only if you need to reason about yourself. relay_compact_and_resume is for clean-seam self-resume after a context advisory: pass workingState and optional ruledOut; Relay owns the objective envelope. relay_recall searches your own archived pre-compaction segments by keyword when a discarded detail is needed. relay_spawn_targets / relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present, call relay_spawn_targets FIRST for the live host/provider/model matrix + your quota, then stand up long-living child agents and shut down your own \u2014 find them later with relay_find_agents spawnedBy:me. For replies, prefer relay_reply or relay_send_message; CLI fallback: agent-relay /reply <messageId> \"<your reply>\". If a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide",
|
|
889
|
+
defaultTemplate: "[agent-relay] You are connected to Agent Relay, a real-time message bus between agents and users. When you receive a relay message: read it, do what it asks, and reply through the relay when a text response is needed. Use agent-relay /react <messageId> <emoji> for lightweight acknowledgement or approval. If Relay MCP tools are available, prefer relay_reply, relay_get_message, relay_get_thread, relay_send_message, relay_upload_artifact, relay_attach_artifact, relay_agent_status, relay_find_agents, relay_compact_and_resume, relay_recall, relay_spawn_agent, and relay_shutdown_agent. You never need to know or pass your own agent id \u2014 relay fills it from your token; use relay_whoami only if you need to reason about yourself. relay_compact_and_resume is for clean-seam self-resume after a context advisory: pass workingState and optional ruledOut; Relay owns the objective envelope. relay_recall searches your own archived pre-compaction segments by keyword when a discarded detail is needed. relay_spawn_targets / relay_spawn_agent / relay_shutdown_agent only appear if your profile grants spawning (a live-children quota); when present, call relay_spawn_targets FIRST for the live host/provider/model matrix + your quota, then stand up long-living child agents and shut down your own \u2014 find them later with relay_find_agents spawnedBy:me. For replies, prefer relay_reply or relay_send_message; CLI fallback: agent-relay /reply <messageId> \"<your reply>\". If a delivered message says it was truncated, fetch the full body with: agent-relay get-message <messageId>. For command details, run: agent-relay /guide",
|
|
890
|
+
variables: []
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
slug: "workspace.lifecycle",
|
|
894
|
+
category: "runner",
|
|
895
|
+
title: "Runner Workspace Lifecycle Note",
|
|
896
|
+
description: "Lifecycle note injected by the runner for isolated workspaces.",
|
|
897
|
+
template: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run `agent-relay workspace ready` \u2014 Relay rebases onto the latest {{baseRef}}, lands your work, and pushes for you. If the installed `agent-relay` binary is stale and says the workspace command is unknown, run the repo-local fallback: `bun src/index.ts workspace ready`.", "After `ready`, the status becomes `review_requested` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after `ready` and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
|
|
898
|
+
defaultTemplate: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run `agent-relay workspace ready` \u2014 Relay rebases onto the latest {{baseRef}}, lands your work, and pushes for you. If the installed `agent-relay` binary is stale and says the workspace command is unknown, run the repo-local fallback: `bun src/index.ts workspace ready`.", "After `ready`, the status becomes `review_requested` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after `ready` and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
|
|
899
|
+
variables: ["branch", "baseRef"]
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
slug: "workspace.deps.symlink",
|
|
903
|
+
category: "runner",
|
|
904
|
+
title: "Runner Workspace Deps Symlink Note",
|
|
905
|
+
description: "Symlinked node_modules caveat injected by the runner.",
|
|
906
|
+
template: "[agent-relay] Isolated workspace: this is a git worktree, and its node_modules are SYMLINKED from the main checkout \u2014 dependencies are already installed and ready to use. Do NOT run a clean dependency install (`bun install` / `npm install` / `pnpm install`): it writes through the symlink and mutates the main checkout's shared node_modules. Build caches written under node_modules are shared too. If typecheck/build fails on a missing module (a dependency added to the base AFTER this worktree was created), run `agent-relay workspace deps` \u2014 it re-provisions only the stale dirs with a real isolated install, safely, without touching the shared node_modules. If you genuinely need to change dependencies in isolation, ask the host to spawn with AGENT_RELAY_WORKSPACE_DEPS=install.",
|
|
907
|
+
defaultTemplate: "[agent-relay] Isolated workspace: this is a git worktree, and its node_modules are SYMLINKED from the main checkout \u2014 dependencies are already installed and ready to use. Do NOT run a clean dependency install (`bun install` / `npm install` / `pnpm install`): it writes through the symlink and mutates the main checkout's shared node_modules. Build caches written under node_modules are shared too. If typecheck/build fails on a missing module (a dependency added to the base AFTER this worktree was created), run `agent-relay workspace deps` \u2014 it re-provisions only the stale dirs with a real isolated install, safely, without touching the shared node_modules. If you genuinely need to change dependencies in isolation, ask the host to spawn with AGENT_RELAY_WORKSPACE_DEPS=install.",
|
|
908
|
+
variables: []
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
slug: "workspace.deps.none",
|
|
912
|
+
category: "runner",
|
|
913
|
+
title: "Runner Workspace Deps None Note",
|
|
914
|
+
description: "Missing dependency provisioning caveat injected by the runner.",
|
|
915
|
+
template: "[agent-relay] Isolated workspace: dependencies were not provisioned (AGENT_RELAY_WORKSPACE_DEPS=none). You may need to install node_modules before typecheck/test/build work.",
|
|
916
|
+
defaultTemplate: "[agent-relay] Isolated workspace: dependencies were not provisioned (AGENT_RELAY_WORKSPACE_DEPS=none). You may need to install node_modules before typecheck/test/build work.",
|
|
917
|
+
variables: []
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
slug: "workspace.symlinks",
|
|
921
|
+
category: "runner",
|
|
922
|
+
title: "Runner Workspace Symlink Note",
|
|
923
|
+
description: "Write-through symlink caveat injected by the runner.",
|
|
924
|
+
template: "[agent-relay] Isolated workspace: these untracked paths are SYMLINKED from the main checkout: {{linked}}. They resolve to the real files in main, so editing or deleting them writes THROUGH to main \u2014 treat them as read-only unless you intend to change main.",
|
|
925
|
+
defaultTemplate: "[agent-relay] Isolated workspace: these untracked paths are SYMLINKED from the main checkout: {{linked}}. They resolve to the real files in main, so editing or deleting them writes THROUGH to main \u2014 treat them as read-only unless you intend to change main.",
|
|
926
|
+
variables: ["linked"]
|
|
927
|
+
}
|
|
928
|
+
];
|
|
853
929
|
|
|
854
|
-
|
|
930
|
+
// runner/src/template-resolver.ts
|
|
931
|
+
var RUNNER_DEFAULTS = new Map(RUNNER_PROMPT_TEMPLATE_DEFAULTS.map((template) => [template.slug, template.defaultTemplate]));
|
|
932
|
+
var templateCache = new Map;
|
|
933
|
+
function renderValue(value) {
|
|
934
|
+
return value ?? "";
|
|
935
|
+
}
|
|
936
|
+
function applyVariables(template, vars) {
|
|
937
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => renderValue(vars[key]));
|
|
938
|
+
}
|
|
939
|
+
function renderRunnerTemplate(slug, vars = {}) {
|
|
940
|
+
const template = templateCache.get(slug) ?? RUNNER_DEFAULTS.get(slug);
|
|
941
|
+
if (template === undefined)
|
|
942
|
+
throw new Error(`unknown runner prompt template slug: ${slug}`);
|
|
943
|
+
return applyVariables(template, vars);
|
|
944
|
+
}
|
|
855
945
|
|
|
856
|
-
|
|
946
|
+
// runner/src/relay-instructions.ts
|
|
947
|
+
function claudeRelayContext() {
|
|
948
|
+
return renderRunnerTemplate("relay.context.claude");
|
|
949
|
+
}
|
|
950
|
+
function claudeReadOnlyRelayContext() {
|
|
951
|
+
return renderRunnerTemplate("relay.context.claude.readonly");
|
|
952
|
+
}
|
|
857
953
|
function relayReplyActionText(messageId) {
|
|
858
954
|
return `relay_reply (messageId: ${messageId}, body: "<your reply>"). CLI fallback: agent-relay /reply ${messageId} "<your reply>"`;
|
|
859
955
|
}
|
|
@@ -865,9 +961,9 @@ function workspaceDepsNote(input) {
|
|
|
865
961
|
return "";
|
|
866
962
|
switch (input.depsMode) {
|
|
867
963
|
case "symlink":
|
|
868
|
-
return "
|
|
964
|
+
return renderRunnerTemplate("workspace.deps.symlink");
|
|
869
965
|
case "none":
|
|
870
|
-
return "
|
|
966
|
+
return renderRunnerTemplate("workspace.deps.none");
|
|
871
967
|
default:
|
|
872
968
|
return "";
|
|
873
969
|
}
|
|
@@ -877,17 +973,12 @@ function workspaceLifecycleNote(input) {
|
|
|
877
973
|
return "";
|
|
878
974
|
const branch = input.branch ? `\`${input.branch}\`` : "an isolated agent branch";
|
|
879
975
|
const base = input.baseRef ? `\`${input.baseRef}\`` : "the base branch";
|
|
880
|
-
return
|
|
881
|
-
`[agent-relay] Isolated workspace: you are in a git worktree on branch ${branch}, based on ${base} \u2014 NOT the main checkout. Other agents may work in parallel and land to ${base}, so ${base} will move under you. That is expected; don't fight it.`,
|
|
882
|
-
`Do NOT push this branch yourself \u2014 not with \`git push\`, not with \`tl push\` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run \`agent-relay workspace ready\` \u2014 Relay rebases onto the latest ${base}, lands your work, and pushes for you. If the installed \`agent-relay\` binary is stale and says the workspace command is unknown, run the repo-local fallback: \`bun src/index.ts workspace ready\`.`,
|
|
883
|
-
`After \`ready\`, the status becomes \`review_requested\` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after \`ready\` and go idle; do NOT wait or poll the steward queue. Relay wakes you with \`landed-success\` when your branch lands and refreshes, or \`landed-failure\` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a \`--N\` suffix \u2014 expected, keep working there. Never \`cd\` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. \`agent-relay workspace status\` anytime shows your current state and the exact next step.`
|
|
884
|
-
].join(`
|
|
885
|
-
`);
|
|
976
|
+
return renderRunnerTemplate("workspace.lifecycle", { branch, baseRef: base });
|
|
886
977
|
}
|
|
887
978
|
function workspaceSymlinksNote(linked) {
|
|
888
979
|
if (!linked.length)
|
|
889
980
|
return "";
|
|
890
|
-
return
|
|
981
|
+
return renderRunnerTemplate("workspace.symlinks", { linked: linked.join(", ") });
|
|
891
982
|
}
|
|
892
983
|
function workspaceDepsNoteFromEnv(env = process.env) {
|
|
893
984
|
const json = env.AGENT_RELAY_WORKSPACE_JSON;
|
|
@@ -948,7 +1039,7 @@ function providerAttachmentText(message) {
|
|
|
948
1039
|
const refs = attachmentRefs(message);
|
|
949
1040
|
if (!refs.length)
|
|
950
1041
|
return;
|
|
951
|
-
const
|
|
1042
|
+
const lines2 = refs.flatMap((ref) => {
|
|
952
1043
|
const artifactId = typeof ref.artifactId === "string" ? ref.artifactId : "unknown-artifact";
|
|
953
1044
|
const source = isRecord(ref.ref) ? [
|
|
954
1045
|
typeof ref.ref.provider === "string" ? ref.ref.provider : undefined,
|
|
@@ -974,7 +1065,7 @@ function providerAttachmentText(message) {
|
|
|
974
1065
|
];
|
|
975
1066
|
return [
|
|
976
1067
|
"Attachments:",
|
|
977
|
-
...
|
|
1068
|
+
...lines2,
|
|
978
1069
|
...guidance
|
|
979
1070
|
].join(`
|
|
980
1071
|
`);
|
|
@@ -984,18 +1075,18 @@ function providerAttachmentText(message) {
|
|
|
984
1075
|
var REMINDER_EVERY_DELIVERIES = 5;
|
|
985
1076
|
var TASK_CLAIM_INSTRUCTION = "Claim this task before working it, then update task status when finished.";
|
|
986
1077
|
function stripRelayScaffolding(body) {
|
|
987
|
-
const
|
|
1078
|
+
const lines2 = body.split(`
|
|
988
1079
|
`);
|
|
989
1080
|
const popTrailingBlanks = () => {
|
|
990
|
-
while (
|
|
991
|
-
|
|
1081
|
+
while (lines2.length && (lines2.at(-1) ?? "").trim() === "")
|
|
1082
|
+
lines2.pop();
|
|
992
1083
|
};
|
|
993
1084
|
popTrailingBlanks();
|
|
994
|
-
if (
|
|
995
|
-
|
|
1085
|
+
if (lines2.at(-1) === TASK_CLAIM_INSTRUCTION) {
|
|
1086
|
+
lines2.pop();
|
|
996
1087
|
popTrailingBlanks();
|
|
997
1088
|
}
|
|
998
|
-
return
|
|
1089
|
+
return lines2.join(`
|
|
999
1090
|
`);
|
|
1000
1091
|
}
|
|
1001
1092
|
function isMemoryInjection(message) {
|
|
@@ -1100,7 +1191,7 @@ ws.onmessage = (event) => {
|
|
|
1100
1191
|
const messages2 = payload.messages;
|
|
1101
1192
|
deliveryCount += 1;
|
|
1102
1193
|
if (firstDelivery) {
|
|
1103
|
-
console.log(process.env.AGENT_RELAY_APPROVAL === "read-only" ?
|
|
1194
|
+
console.log(process.env.AGENT_RELAY_APPROVAL === "read-only" ? claudeReadOnlyRelayContext() : claudeRelayContext());
|
|
1104
1195
|
const wsNote = workspaceDepsNoteFromEnv();
|
|
1105
1196
|
if (wsNote) {
|
|
1106
1197
|
console.log("");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Message } from "agent-relay-sdk";
|
|
2
2
|
import { claudeProviderMessageText } from "../../../src/adapters/claude-delivery";
|
|
3
|
-
import {
|
|
3
|
+
import { claudeReadOnlyRelayContext, claudeRelayContext, workspaceDepsNoteFromEnv } from "../../../src/relay-instructions";
|
|
4
4
|
|
|
5
5
|
const port = process.env.AGENT_RELAY_RUNNER_PORT;
|
|
6
6
|
if (!port) process.exit(0);
|
|
@@ -15,7 +15,7 @@ ws.onmessage = (event) => {
|
|
|
15
15
|
const messages = payload.messages as Message[];
|
|
16
16
|
deliveryCount += 1;
|
|
17
17
|
if (firstDelivery) {
|
|
18
|
-
console.log(process.env.AGENT_RELAY_APPROVAL === "read-only" ?
|
|
18
|
+
console.log(process.env.AGENT_RELAY_APPROVAL === "read-only" ? claudeReadOnlyRelayContext() : claudeRelayContext());
|
|
19
19
|
const wsNote = workspaceDepsNoteFromEnv();
|
|
20
20
|
if (wsNote) {
|
|
21
21
|
console.log("");
|
package/runner/src/adapter.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { LivenessInputs } from "./liveness";
|
|
|
4
4
|
import type { SessionEvent } from "./session-insights";
|
|
5
5
|
import { messageBodyMaxCharsFromEnv } from "./config";
|
|
6
6
|
import { relayReplyReminderText } from "./relay-instructions";
|
|
7
|
+
import { renderRunnerTemplate } from "./template-resolver";
|
|
7
8
|
|
|
8
9
|
export type SemanticStatus = "idle" | "busy" | "offline" | "error";
|
|
9
10
|
type ProviderWorkKind = "provider-turn" | "subagent" | "background-script";
|
|
@@ -226,7 +227,9 @@ export function profileIsVanillaBase(config: Pick<RunnerSpawnConfig, "agentProfi
|
|
|
226
227
|
return config.agentProfile?.base === "vanilla";
|
|
227
228
|
}
|
|
228
229
|
|
|
229
|
-
export
|
|
230
|
+
export function relayContext(): string {
|
|
231
|
+
return renderRunnerTemplate("relay.context.codex");
|
|
232
|
+
}
|
|
230
233
|
|
|
231
234
|
// #306 — deliver the FULL message body by default. Only a pathological body beyond this
|
|
232
235
|
// high cap truncates (with a get-message hint) so it can't nuke an agent's context; the 99%
|
|
@@ -2,6 +2,7 @@ import { emitRelayEvent } from "./events";
|
|
|
2
2
|
import { routeNotification } from "./notification-router";
|
|
3
3
|
import { getAgent, markAgentReadyNotified, recordCrashReport, recordTerminalEvent } from "./db";
|
|
4
4
|
import { crashReportFor } from "./agent-death-diagnosis";
|
|
5
|
+
import { renderTemplate } from "./prompt-resolver";
|
|
5
6
|
import type { AgentCard, AgentDeathDiagnosis } from "./types";
|
|
6
7
|
|
|
7
8
|
/** One-line cause tag appended to a death notification body, e.g. " · why: Clean exit (exit code 0)". */
|
|
@@ -122,7 +123,11 @@ export function notifyAgentReady(childId: string): void {
|
|
|
122
123
|
recipients: [parent],
|
|
123
124
|
message: {
|
|
124
125
|
subject: "Spawn recovered",
|
|
125
|
-
body:
|
|
126
|
+
body: renderTemplate("notify.spawn.recovered", {
|
|
127
|
+
spawnRequestId: spawnRequestId ? ` for \`${spawnRequestId}\`` : "",
|
|
128
|
+
label: describe(child),
|
|
129
|
+
childId,
|
|
130
|
+
}),
|
|
126
131
|
payload: { kind: "agent.spawn_recovered", agentId: childId, parent, provider: providerOf(child), spawnRequestId, supersedes: "agent.spawn_failed" },
|
|
127
132
|
replyExpected: false,
|
|
128
133
|
},
|
|
@@ -135,7 +140,7 @@ export function notifyAgentReady(childId: string): void {
|
|
|
135
140
|
recipients: [parent],
|
|
136
141
|
message: {
|
|
137
142
|
subject: "Spawned agent ready",
|
|
138
|
-
body:
|
|
143
|
+
body: renderTemplate("notify.agent.ready", { label: describe(child), childId }),
|
|
139
144
|
payload: { kind: "agent.ready", agentId: childId, parent, provider: providerOf(child), spawnRequestId },
|
|
140
145
|
replyExpected: false,
|
|
141
146
|
},
|
|
@@ -178,7 +183,11 @@ export function notifyAgentOffline(childId: string, reason: string, diagnosis?:
|
|
|
178
183
|
recipients: [parent],
|
|
179
184
|
message: {
|
|
180
185
|
subject: "Spawned agent exited",
|
|
181
|
-
body:
|
|
186
|
+
body: renderTemplate("notify.agent.exited", {
|
|
187
|
+
label: describe(child),
|
|
188
|
+
reason,
|
|
189
|
+
diagnosis: diagnosisSuffix(diagnosis),
|
|
190
|
+
}),
|
|
182
191
|
payload: { kind: "agent.exited", agentId: childId, parent, reason, ...(diagnosis ? { diagnosis } : {}) },
|
|
183
192
|
replyExpected: false,
|
|
184
193
|
},
|
|
@@ -201,7 +210,11 @@ export function notifyAgentOffline(childId: string, reason: string, diagnosis?:
|
|
|
201
210
|
recipients: [parent],
|
|
202
211
|
message: {
|
|
203
212
|
subject: "Spawn failed",
|
|
204
|
-
body:
|
|
213
|
+
body: renderTemplate("notify.agent.never-ready", {
|
|
214
|
+
label: describe(child),
|
|
215
|
+
reason,
|
|
216
|
+
diagnosis: diagnosisSuffix(diagnosis),
|
|
217
|
+
}),
|
|
205
218
|
payload: { kind: "agent.spawn_failed", agentId: childId, parent, reason, ...(diagnosis ? { diagnosis } : {}) },
|
|
206
219
|
replyExpected: false,
|
|
207
220
|
},
|
|
@@ -231,7 +244,10 @@ export function notifyAgentSpawnFailed(input: { parent: string; spawnRequestId?:
|
|
|
231
244
|
recipients: [input.parent],
|
|
232
245
|
message: {
|
|
233
246
|
subject: "Spawn failed",
|
|
234
|
-
body:
|
|
247
|
+
body: renderTemplate("notify.spawn.failed", {
|
|
248
|
+
spawnRequestId: input.spawnRequestId ? ` \`${input.spawnRequestId}\`` : "",
|
|
249
|
+
reason: input.reason,
|
|
250
|
+
}),
|
|
235
251
|
payload: { kind: "agent.spawn_failed", parent: input.parent, spawnRequestId: input.spawnRequestId, provider: input.provider, reason: input.reason },
|
|
236
252
|
replyExpected: false,
|
|
237
253
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createCommand } from "../commands-db";
|
|
2
2
|
import { getAgent, getTask, listOrchestrators, sendMessage, updateTaskStatus } from "../db";
|
|
3
|
+
import { renderTemplate } from "../prompt-resolver";
|
|
3
4
|
import { CLOSED_TASK_STATUS } from "./constants";
|
|
4
5
|
import {
|
|
5
6
|
ensureAutomationTables,
|
|
@@ -132,7 +133,9 @@ function maybeWarnRuntimeBudget(run: AutomationRun, task: Task, now: number): Me
|
|
|
132
133
|
to: target,
|
|
133
134
|
kind: "control",
|
|
134
135
|
subject: "Automation runtime budget warning",
|
|
135
|
-
body: budget.warningMessage ??
|
|
136
|
+
body: budget.warningMessage ?? renderTemplate("notify.automation.budget-warning", {
|
|
137
|
+
remainingMinutes: `${remainingMinutes} minute${remainingMinutes === 1 ? "" : "s"}`,
|
|
138
|
+
}),
|
|
136
139
|
payload: {
|
|
137
140
|
automationId: run.automationId,
|
|
138
141
|
automationRunId: run.id,
|
package/src/branch-landed.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { emitRelayEvent } from "./events";
|
|
|
3
3
|
import { routeNotification, INFORMATIONAL_TTL_MS } from "./notification-router";
|
|
4
4
|
import { registerDefaultAudienceRules, audiencePredicates } from "./notification-rules";
|
|
5
5
|
import { createActivityEvent, getAgent, updateWorkspaceStatus } from "./db";
|
|
6
|
+
import { renderTemplate } from "./prompt-resolver";
|
|
6
7
|
import { handleLandedIssueLifecycle } from "./services/issue-lifecycle";
|
|
7
8
|
import type { WorkspaceMergePreview, WorkspaceRecord } from "./types";
|
|
8
9
|
|
|
@@ -170,7 +171,13 @@ export function notifyBranchLanded(input: BranchLandedInput): void {
|
|
|
170
171
|
ttlMs: null,
|
|
171
172
|
message: {
|
|
172
173
|
subject: "Your branch landed",
|
|
173
|
-
body:
|
|
174
|
+
body: renderTemplate("notify.land.success", {
|
|
175
|
+
branch: branchLabel,
|
|
176
|
+
base,
|
|
177
|
+
sha: `${shaLabel}${subjectLabel}`,
|
|
178
|
+
publishLabel,
|
|
179
|
+
continueLabel,
|
|
180
|
+
}),
|
|
174
181
|
payload: { ...payload, notification: "landed-success" },
|
|
175
182
|
replyExpected: false,
|
|
176
183
|
},
|
|
@@ -241,7 +248,7 @@ export function notifyBranchLandFailed(input: {
|
|
|
241
248
|
ttlMs: null,
|
|
242
249
|
message: {
|
|
243
250
|
subject: "Workspace land failed",
|
|
244
|
-
body:
|
|
251
|
+
body: renderTemplate("notify.land.failure", { branch: branchLabel, base, reason }),
|
|
245
252
|
payload,
|
|
246
253
|
replyExpected: false,
|
|
247
254
|
},
|
|
@@ -300,7 +307,12 @@ export function notifyStewardHandoff(input: {
|
|
|
300
307
|
ttlMs: null,
|
|
301
308
|
message: {
|
|
302
309
|
subject: "Your merge is now with a steward",
|
|
303
|
-
body:
|
|
310
|
+
body: renderTemplate("notify.land.steward-taken", {
|
|
311
|
+
branch: branchLabel,
|
|
312
|
+
base,
|
|
313
|
+
stewardId: input.stewardId,
|
|
314
|
+
reasonLabel,
|
|
315
|
+
}),
|
|
304
316
|
payload: { ...payload, notification: "steward-handoff" },
|
|
305
317
|
replyExpected: false,
|
|
306
318
|
},
|
|
@@ -318,7 +330,13 @@ export function notifyStewardHandoff(input: {
|
|
|
318
330
|
ttlMs: null,
|
|
319
331
|
message: {
|
|
320
332
|
subject: "A branch agent's merge is now with a steward",
|
|
321
|
-
body:
|
|
333
|
+
body: renderTemplate("notify.land.steward-taken.coordinator", {
|
|
334
|
+
owner: owner ?? "?",
|
|
335
|
+
branch: branchLabel,
|
|
336
|
+
base,
|
|
337
|
+
stewardId: input.stewardId,
|
|
338
|
+
reasonLabel,
|
|
339
|
+
}),
|
|
322
340
|
payload: { ...payload, notification: "steward-handoff", audience: "coordinator" },
|
|
323
341
|
replyExpected: false,
|
|
324
342
|
},
|
package/src/config-store.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { DEFAULT_SELF_RESUME_CONFIG, validateSelfResumeConfig } from "./self-resume-config";
|
|
12
12
|
import { TEAM_TEMPLATE_NAMESPACE, validateTeamTemplate } from "./team-template-config";
|
|
13
13
|
import { PROJECT_INSTRUCTION_POLICY_NAMES } from "./project-instructions";
|
|
14
|
+
import { renderTemplate } from "./prompt-resolver";
|
|
14
15
|
import { PROJECT_MCP_POLICY_NAMES } from "./project-mcp";
|
|
15
16
|
import { PROJECT_SETTINGS_POLICY_NAMES } from "./project-settings";
|
|
16
17
|
import { PROJECT_SKILLS_POLICY_NAMES } from "./project-skills";
|
|
@@ -60,28 +61,23 @@ const VALID_PERMISSION_MODES = ["open", "guarded", "read-only"] as const;
|
|
|
60
61
|
const VALID_POLICY_MODES = ["always-on", "on-demand"] as const;
|
|
61
62
|
const VALID_MANAGED_STATUSES = ["stopped", "starting", "running", "stopping", "backoff", "failed"] as const;
|
|
62
63
|
export const DEFAULT_SPAWN_PROFILE_NAME = "default-relay";
|
|
63
|
-
const COORDINATION_CHARTER = "Coordination charter: stay at helicopter altitude; delegate implementation to spawned workers; write briefs, maintain the blackboard and plan DAG; coordinate, review, and release; do not drift into mechanical edits when a worker can do them; protect your context for coordination.";
|
|
64
|
-
const OPS_ASSISTANT_BRIEF = [
|
|
65
|
-
"Ops-assistant standing brief: execute mechanical operations on your coordinator's behalf: test suites, release commands such as `bun run release`, smoke and verification probes, gates, and host/version/status checks.",
|
|
66
|
-
"When running a self-verifying long script, especially `bun run release` (gates -> CI-watch -> deploy -> host-verify, then one final result), redirect its output to a file rather than letting it stream into your context: `bun run release > /tmp/ar-release.log 2>&1`. Wait for it to exit, then read only the final result with `tail /tmp/ar-release.log`. Never run it with inline streaming output. Do not poll, monitor, tail logs, run parallel status/version checks, or otherwise babysit it while it runs. Trust the script; act only on its final result. This generalizes to any long-running command whose intermediate output you do not need.",
|
|
67
|
-
"Hold and apply session-local ops conventions the coordinator gives you. Report crisp outcomes only: command/gate, pass/fail, commit/version/host evidence, and the next blocker when one exists. Do not dump logs unless asked.",
|
|
68
|
-
"Do not make strategy, design, scope, or release-timing calls. Escalate those decisions to your spawn-parent. Report operational results to your spawn-parent by default and keep their context clean.",
|
|
69
|
-
].join("\n");
|
|
70
64
|
const BUILT_IN_AGENT_PROFILE_NAMES = new Set([DEFAULT_SPAWN_PROFILE_NAME, "minimal", "isolated-research", "vanilla", "lean-worker", "coordinator", "ops-assistant", "worker", "reviewer"]);
|
|
71
65
|
// Tool group set for worker-style profiles (#551): excludes spawn/coordinator-only surface.
|
|
72
66
|
const WORKER_TOOL_GROUPS = ["core", "workspace", "memory", "artifacts", "blackboard", "subscriptions"];
|
|
73
67
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
function builtInAgentProfiles(): AgentProfile[] {
|
|
69
|
+
return [
|
|
70
|
+
agentProfileDefaults({ name: DEFAULT_SPAWN_PROFILE_NAME, icon: "🤖", description: "Relay-aware spawned agent with bundled Relay context, skills, plugins, and status integration.", base: "host", mcp: { mode: "none" }, relay: { tools: { groups: WORKER_TOOL_GROUPS } } }),
|
|
71
|
+
agentProfileDefaults({ name: "minimal", icon: "▫️", description: "Repo-focused spawned agent with Relay affordances disabled and host/global extras avoided where supported.", base: "minimal" }),
|
|
72
|
+
agentProfileDefaults({ name: "isolated-research", icon: "🔬", description: "Strict profile for autonomous research runs: repo context only, no Relay prompt surface, no global extras where supported.", base: "isolated" }),
|
|
73
|
+
agentProfileDefaults({ name: "vanilla", icon: "🫥", description: "Zero host bleed (#552): isolated provider home with host plugins, skills, hooks, MCP, and the CLAUDE.md/AGENTS.md cascade (repo + global) all suppressed at launch. Relay provisions any surface explicitly per profile.", base: "vanilla" }),
|
|
74
|
+
agentProfileDefaults({ name: "lean-worker", icon: "🪶", description: "Lean worker profile (#570): vanilla host-dependency drop with only Relay context/MCP restored and Project knowledge injected from the spawn cwd.", base: "vanilla", relay: { context: true, skills: false, plugins: false, statusLine: false, mcp: true, tools: { groups: WORKER_TOOL_GROUPS } }, projectInstructions: { policy: "all" } }),
|
|
75
|
+
agentProfileDefaults({ name: "coordinator", icon: "🧭", description: "Relay-aware coordination lead for spawning worker teams, maintaining shared state, and driving review and release.", base: "host", instructions: { append: [renderTemplate("profile.coordinator.charter")], repoInstructions: "allow", globalInstructions: "allow" }, maxSpawnedAgents: 8 }),
|
|
76
|
+
agentProfileDefaults({ name: "ops-assistant", icon: "🛠️", description: "Relay-aware ops lieutenant for a coordinator: runs tests, releases, smoke probes, gates, and host checks, then reports crisp outcomes back up.", provider: "codex", base: "host", instructions: { append: [renderTemplate("profile.ops-assistant.brief")], repoInstructions: "allow", globalInstructions: "allow" }, permissions: { mode: "open", filesystem: "host" } }),
|
|
77
|
+
agentProfileDefaults({ name: "worker", icon: "🔨", description: "Relay-aware focused implementer with open approval posture and no delegated spawn capability.", base: "host", instructions: { system: renderTemplate("profile.worker.system"), append: [], repoInstructions: "allow", globalInstructions: "allow" }, mcp: { mode: "none" }, permissions: { mode: "open", filesystem: "host" }, relay: { tools: { groups: WORKER_TOOL_GROUPS } } }),
|
|
78
|
+
agentProfileDefaults({ name: "reviewer", icon: "🔍", description: "Relay-aware read-only reviewer for audit and review passes without file writes.", base: "host", mcp: { mode: "none" }, permissions: { mode: "read-only", filesystem: "host" }, relay: { tools: { groups: WORKER_TOOL_GROUPS } } }),
|
|
79
|
+
].map((profile) => ({ ...profile, builtIn: true }));
|
|
80
|
+
}
|
|
85
81
|
|
|
86
82
|
interface ConfigRow {
|
|
87
83
|
namespace: string;
|
|
@@ -455,7 +451,7 @@ function agentProfileOverrideDiff(base: unknown, merged: unknown): unknown {
|
|
|
455
451
|
}
|
|
456
452
|
|
|
457
453
|
function validateAgentProfileOverride(key: string, value: unknown): Partial<AgentProfile> {
|
|
458
|
-
const builtIn =
|
|
454
|
+
const builtIn = builtInAgentProfiles().find((profile) => profile.name === key);
|
|
459
455
|
if (!builtIn) throw new ValidationError("agent profile override target must be a built-in profile");
|
|
460
456
|
if (!isRecord(value)) throw new ValidationError("agent-profile override value must be an object");
|
|
461
457
|
const merged = mergeProfileOverride(builtIn, { ...value, name: key, builtIn: true });
|
|
@@ -891,7 +887,7 @@ function builtInProfileEntry(profile: AgentProfile): ConfigEntry<AgentProfile> {
|
|
|
891
887
|
}
|
|
892
888
|
|
|
893
889
|
export function getAgentProfile(name: string): ConfigEntry<AgentProfile> | null {
|
|
894
|
-
const builtIn =
|
|
890
|
+
const builtIn = builtInAgentProfiles().find((profile) => profile.name === name);
|
|
895
891
|
if (builtIn) return builtInProfileEntry(builtIn);
|
|
896
892
|
return getConfig<AgentProfile>(AGENT_PROFILE_NAMESPACE, name);
|
|
897
893
|
}
|
|
@@ -922,7 +918,7 @@ export function toolGroupsForProfile(profileName: string | undefined): string[]
|
|
|
922
918
|
export function listAgentProfiles(): ConfigEntry<AgentProfile>[] {
|
|
923
919
|
const custom = listConfig<AgentProfile>(AGENT_PROFILE_NAMESPACE);
|
|
924
920
|
return [
|
|
925
|
-
...
|
|
921
|
+
...builtInAgentProfiles().map(builtInProfileEntry),
|
|
926
922
|
...custom,
|
|
927
923
|
].sort((a, b) => Number(Boolean(b.value.builtIn)) - Number(Boolean(a.value.builtIn)) || a.key.localeCompare(b.key));
|
|
928
924
|
}
|
package/src/context-advisory.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { NOTIFICATIONS_DEFAULT_KEY, NOTIFICATIONS_NAMESPACE, notificationAgentKe
|
|
|
7
7
|
import { getDb } from "./db/connection.ts";
|
|
8
8
|
import { emitRelayEvent } from "./events";
|
|
9
9
|
import { routeNotification } from "./notification-router";
|
|
10
|
+
import { renderTemplate } from "./prompt-resolver";
|
|
10
11
|
import { effectiveProviderCatalogList } from "./provider-catalog-store";
|
|
11
12
|
import type { AgentCard, Command, ContextState } from "./types";
|
|
12
13
|
|
|
@@ -71,12 +72,18 @@ function notifyContextAdvisory(input: ContextAdvisoryInput): void {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function contextAdvisoryBody(input: ContextAdvisoryInput): string {
|
|
74
|
-
const
|
|
75
|
+
const tokens = `${formatTokens(input.tokens)} / ${formatTokens(input.windowTokens)} tokens used`;
|
|
76
|
+
const percent = `${input.percent}% of window, ${input.band}% band`;
|
|
75
77
|
const threshold = `resolved self-compact threshold ${input.selfCompactPercent}% (${input.thresholdSource})`;
|
|
76
78
|
if (input.stage === "warn") {
|
|
77
|
-
return
|
|
79
|
+
return renderTemplate("context.advisory.warn", { tokens, percent, threshold });
|
|
78
80
|
}
|
|
79
|
-
return
|
|
81
|
+
return renderTemplate("context.advisory.compact", {
|
|
82
|
+
tokens,
|
|
83
|
+
percent,
|
|
84
|
+
threshold,
|
|
85
|
+
hardBackstop: `${input.hardBackstopPercent}%`,
|
|
86
|
+
});
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
function contextAdvisoryBand(utilization: number, threshold: EffectiveContextThreshold): { band: number; stage: ContextAdvisoryStage } | null {
|
package/src/db/index.ts
CHANGED
package/src/db/projects.ts
CHANGED
|
@@ -247,6 +247,35 @@ export function resolveProjectForCwd(cwd: string): Project | null {
|
|
|
247
247
|
return best ? getProject(best.projectId) : null;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
/** Batch cwd→project resolution using the same longest-root-match rule as the scalar helper. */
|
|
251
|
+
export function resolveProjectsForCwds(cwds: string[]): Map<string, Project> {
|
|
252
|
+
const requested = [...new Set(cwds.filter(Boolean))];
|
|
253
|
+
const resolved = new Map<string, { projectId: string; len: number }>();
|
|
254
|
+
if (requested.length === 0) return new Map();
|
|
255
|
+
const rows = getDb()
|
|
256
|
+
.query("SELECT project_id, root_path, created_at FROM project_roots")
|
|
257
|
+
.all() as ProjectRootRow[];
|
|
258
|
+
for (const cwd of requested) {
|
|
259
|
+
for (const row of rows) {
|
|
260
|
+
const current = resolved.get(cwd);
|
|
261
|
+
if (isPathWithinBase(cwd, row.root_path) && (!current || row.root_path.length > current.len)) {
|
|
262
|
+
resolved.set(cwd, { projectId: row.project_id, len: row.root_path.length });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const mapped = new Map<string, Project>();
|
|
267
|
+
const projectsById = new Map<string, Project>();
|
|
268
|
+
for (const [cwd, match] of resolved.entries()) {
|
|
269
|
+
let project = projectsById.get(match.projectId);
|
|
270
|
+
if (!project) {
|
|
271
|
+
project = getProject(match.projectId) ?? undefined;
|
|
272
|
+
if (project) projectsById.set(match.projectId, project);
|
|
273
|
+
}
|
|
274
|
+
if (project) mapped.set(cwd, project);
|
|
275
|
+
}
|
|
276
|
+
return mapped;
|
|
277
|
+
}
|
|
278
|
+
|
|
250
279
|
// ── Composition graph (#406) ────────────────────────────────────────────────
|
|
251
280
|
// project_edges models two relations, asymmetric by design:
|
|
252
281
|
// • contains — umbrella → sub-project hierarchy. INHERITS: resolving a
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defaultPromptTemplates } from "../prompt-resolver";
|
|
2
|
+
import { seedTemplates } from "./prompt-templates";
|
|
3
|
+
|
|
4
|
+
export const PROMPT_TEMPLATES_SCHEMA_SQL = `
|
|
5
|
+
CREATE TABLE IF NOT EXISTS prompt_templates (
|
|
6
|
+
slug TEXT PRIMARY KEY,
|
|
7
|
+
category TEXT NOT NULL,
|
|
8
|
+
title TEXT NOT NULL,
|
|
9
|
+
description TEXT,
|
|
10
|
+
template TEXT NOT NULL,
|
|
11
|
+
default_template TEXT NOT NULL,
|
|
12
|
+
variables TEXT NOT NULL DEFAULT '[]',
|
|
13
|
+
updated_at INTEGER NOT NULL,
|
|
14
|
+
created_at INTEGER NOT NULL
|
|
15
|
+
);
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export function seedPromptTemplatesIfNeeded(): void {
|
|
19
|
+
seedTemplates(defaultPromptTemplates());
|
|
20
|
+
}
|