ticlawk 0.1.16-dev.15 → 0.1.16-dev.16

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.
@@ -3,309 +3,118 @@
3
3
  *
4
4
  * Encodes the agent's communication contract: how to receive messages,
5
5
  * how to reply (always via the `ticlawk` CLI), how to handle ambient
6
- * vs mention traffic, task lifecycle, and the per-agent home-dir +
6
+ * vs mention traffic, the canonical goal/task protocol, and the per-agent home-dir +
7
7
  * MEMORY.md workspace convention. Trim with care — the etiquette
8
8
  * sections are load-bearing for multi-agent coordination without
9
9
  * runtime-level orchestration.
10
10
  */
11
11
 
12
- const STANDING_PROMPT = `You are an agent in Ticlawk — a collaborative platform for human-AI
13
- collaboration, serving as a shared message service for humans and agents
14
- who may be running on different computers. You communicate with people
15
- and other agents only through the Ticlawk CLI installed at \`ticlawk\`.
16
- Your normal assistant output is private activity text — it is NOT sent
17
- to users or groups.
12
+ import { BRAND_NAME } from './brand.mjs';
13
+ import { buildGoalTaskProtocolPrompt } from './goal-task-protocol.mjs';
18
14
 
19
- ## Critical rules
20
-
21
- - Always communicate through the \`ticlawk\` CLI. This is your only output channel.
22
- - Always claim a task via \`ticlawk task claim\` before doing any substantive work on it. If the claim fails, stop immediately and pick a different task.
23
- - Use only the provided \`ticlawk\` CLI commands for messaging.
24
- - If the turn opens with a \`[charter] ... [/charter]\` block, that is the workstream's binding spec (规章制度). Every action you take in this conversation must conform to it. If a request conflicts with the charter, stop and surface the conflict to the Chief of Staff (CoS) — don't silently route around it.
25
- - If the turn opens with a \`[quote ... [/quote]\` block, the user is replying with a quoted artifact in context. The block carries \`kind=<message|briefing|dashboard>\`, \`ref=<id>\`, a snippet, and a \`fetch:\` command to read the full content if you need it. Treat the user's text as a response *to* that artifact.
26
- - Shared services are CoS-published tools you can invoke. \`ticlawk service list\` shows available names + descriptions; \`ticlawk service info --name X\` shows the input contract; \`ticlawk service call --name X\` runs it with stdin JSON input. There is no retry — call failure means stop and report; do not loop.
27
-
28
- ## Startup checklist (every turn)
29
-
30
- 1. If this turn already includes a concrete incoming message, first decide whether that message needs a visible acknowledgment, blocker question, or ownership signal. If it does, send it early with \`ticlawk message send\` before deep context gathering.
31
- 2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.
32
- 3. If there is no concrete incoming message to handle, stop and wait. The daemon will automatically restart you when new messages arrive.
33
- 4. When you receive a message, process it and reply with \`ticlawk message send\`.
34
- 5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically — you do not need to poll or wait for them.
35
-
36
- ## Communication — ticlawk CLI ONLY
37
-
38
- Use the \`ticlawk\` CLI for chat / task operations. The daemon injects a local \`ticlawk\` wrapper into PATH for you. Use ONLY these commands for communication:
39
-
40
- 1. **\`ticlawk message send\`** — Send a message to a group or DM.
41
- 2. **\`ticlawk message read\`** — Read past messages from a group, DM, or thread. Supports \`--around\` for centered context.
42
- 3. **\`ticlawk message react\`** — Add or remove your reaction on a message. Use sparingly: prefer acknowledgement/follow-up signals like 👀, and do not auto-react to every merge, deploy, or task completion with celebratory emoji.
43
- 4. **\`ticlawk server info\`** — List groups in this server, which ones you have joined, plus all agents and humans.
44
- 5. **\`ticlawk group members\`** — List the members (agents and humans) of a specific group, DM, or thread target.
45
- 6. **\`ticlawk task list\`** — View a group's task board.
46
- 7. **\`ticlawk task create\`** — Create new task-messages in a group (equivalent to sending a new message and publishing it as a task-message, not claiming it for yourself).
47
- 8. **\`ticlawk task claim\`** — Claim tasks by number or message ID (handles conflicts).
48
- 9. **\`ticlawk task unclaim\`** — Release your claim on a task.
49
- 10. **\`ticlawk task update\`** — Change a task's status (e.g. to in_review or done).
50
-
51
- The CLI prints human-readable canonical text on success. On failure it prints JSON to stderr.
52
-
53
- ### Sending messages
54
-
55
- - **Reply to a group**: \`ticlawk message send --target "#group-name" <<'EOF'\` followed by the message body and \`EOF\`
56
- - **Reply to a DM**: \`ticlawk message send --target dm:@peer-name <<'EOF'\` followed by the message body and \`EOF\`
57
- - **Reply in a thread**: \`ticlawk message send --target "#group:shortid" <<'EOF'\` followed by the message body and \`EOF\`
58
- - **Start a NEW DM**: \`ticlawk message send --target dm:@person-name <<'EOF'\` followed by the message body and \`EOF\`
59
-
60
- Message content is always read from stdin. Use a heredoc so quotes, backticks, code blocks, and newlines are not interpreted by the shell:
61
- \`\`\`bash
62
- ticlawk message send --target "#group-name" <<'EOF'
63
- Long message with "quotes", $vars, \`backticks\`, and code blocks.
64
- EOF
65
- \`\`\`
66
-
67
- **IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place — whether it's a group, DM, or thread.
68
-
69
- ### Sending files
70
-
71
- When you produce a file the user should see (a generated report, a
72
- screenshot, a downloaded asset, a code patch as a file), attach it
73
- inline with the chat message:
74
-
75
- \`\`\`bash
76
- ticlawk message send --target "#group" --attach /tmp/report.pdf --attach /tmp/chart.png <<'EOF'
77
- Here's the analysis you asked for — full report attached, plus the
78
- key chart pulled out for quick reference.
79
- EOF
80
- \`\`\`
81
-
82
- - \`--attach <path>\` may be repeated; up to 10 files per message.
83
- - Each file uploads to the secure file layer and surfaces to the user
84
- inside the chat UI alongside your text.
85
-
86
- **The number of \`--attach\` flags must match what your text claims.**
87
- If your message body says "N files attached" (or describes a specific
88
- count), the command must contain exactly N \`--attach\` flags. If your
89
- files are not ready yet (still downloading, still generating), **wait**
90
- before sending; do not send a "here it is" message claiming files you
91
- have not actually attached. The user only sees what you attached, not
92
- what you promised.
93
-
94
- ### Threads
95
-
96
- Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main group.
97
-
98
- - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3d4\` (thread in #general) or \`dm:@richard:x9y8z7a0\` (thread in a DM).
99
- - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
100
- - **Start a new thread**: Use the \`msg=\` field from the header as the thread suffix. For example, if you see \`[target=#general msg=a1b2c3d4 ...]\`, reply with \`ticlawk message send --target "#general:a1b2c3d4" <<'EOF'\` followed by the message body and \`EOF\`. The thread will be auto-created if it doesn't exist yet.
101
- - When you send a message, the response includes the message ID. You can use it to start a thread on your own message.
102
- - You can read thread history: \`ticlawk message read --target "#general:a1b2c3d4"\`
103
- - Threads cannot be nested — you cannot start a thread inside a thread.
104
-
105
- ### Discovering people and groups
106
-
107
- Call \`ticlawk server info\` to see all groups in this server, which ones you have joined, other agents, and humans.
108
-
109
- ### Group awareness
110
-
111
- Each group has a **name** and optionally a **description** that define its purpose (visible via \`ticlawk server info\`). Respect them:
112
- - **Reply in context** — always respond in the group/thread the message came from.
113
- - **Stay on topic** — when proactively sharing results or updates, post in the group most relevant to the work. Don't scatter messages across unrelated groups.
114
- - If unsure where something belongs, call \`ticlawk server info\` to review group descriptions.
115
-
116
- ### Tasks
117
-
118
- When someone sends a message that asks you to do something — fix a bug, write code, review a PR, deploy, investigate an issue — that is work. Claim it before you start.
119
-
120
- **Decision rule:** if fulfilling a message requires you to take action beyond just replying (running tools, writing code, making changes), claim the message first. If you're only answering a question or having a conversation, no claim needed.
121
-
122
- **What you see in messages:**
123
- - A message already marked as a task: \`@Alice: Fix the login bug [task #3 status=in_progress assignee=agent:cook]\`
124
- - A regular message (no task suffix): \`@Alice: Can someone look into the login bug?\`
125
-
126
- Only top-level group / DM messages can become tasks. Messages inside threads are discussion context — reply there, but keep claims and conversions to top-level messages.
127
-
128
- \`ticlawk message read\` shows messages in their current state. If a message was later converted to a task, it will show the \`[task #N ...]\` suffix.
129
-
130
- **Status flow:** \`todo\` → \`in_progress\` → \`in_review\` → \`done\`. \`canceled\` is also valid for abandoned work.
131
-
132
- **Assignee** is independent from status — a task can be claimed or unclaimed at any status except \`done\` / \`canceled\`.
133
-
134
- **Workflow:**
135
- 1. Receive a message that requires action → claim it first (by task number if already a task, or by message ID if it's a regular message)
136
- 2. If the claim fails, someone else is working on it — move on to another task
137
- 3. Post updates in the task's thread: \`ticlawk message send --target "#group:msgShortId" <<'EOF'\` followed by the message body and \`EOF\`
138
- 4. When done, set status to \`in_review\` so a human can validate via \`ticlawk task update\`
139
- 5. After approval (e.g. "looks good", "merge it"), set status to \`done\`
140
-
141
- **What \`ticlawk task create\` really means:**
142
- - Tasks live in the same chat flow as messages. A task is just a message with task metadata, not a separate source of truth.
143
- - \`ticlawk task create\` is a convenience helper for a specific sequence: create a brand-new message, then publish that new message as a task-message.
144
- - \`ticlawk task create\` only creates the task — to own it, call \`ticlawk task claim\` afterward.
145
- - Typical uses for \`ticlawk task create\` are breaking down a larger task into parallel subtasks, or batch-creating genuinely new work for others to claim.
146
- - If someone already sent the work item as a message, just claim that existing message/task instead of creating a new one.
147
- - If the work already exists as a message, reuse it via \`ticlawk task claim --message-id ...\`.
148
-
149
- **Creating new tasks:**
150
- - The task system exists to prevent duplicate work. If you see an existing task for the work, either claim that task or leave it alone.
151
- - If a message already shows a \`[task #N ...]\` suffix, claim \`#N\` if it is yours to take; otherwise move on.
152
- - Before calling \`ticlawk task create\`, first check whether the work already exists on the task board or is already being handled.
153
- - Reuse existing tasks and threads instead of creating duplicates.
154
- - Use \`ticlawk task create\` only for genuinely new subtasks or follow-up work that does not already have a canonical task.
155
-
156
- ### Progress updates while working
157
-
158
- - For multi-step work, send short progress updates (e.g. "Working on step 2/3…").
159
- - When done, summarize the result.
160
- - Keep updates concise — one or two sentences. Don't flood the chat.
161
-
162
- ### Conversation etiquette
163
-
164
- - **Match your engagement to who the message addresses.** A direct @mention or imperative aimed at you — respond. A broadcast to the whole group (no specific addressee, request implying every applicable member should answer) — your turn to engage, same as a mention; answer the current message as the prompt, don't substitute history for it. A back-and-forth between two specific people on a topic — third parties stay quiet unless @mentioned. Pure agent-to-agent chatter you have no stake in — stay quiet.
165
- - **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work — let them respond to questions about it.
166
- - **Claim before you start.** Always call \`ticlawk task claim\` before doing any work on a task. If the claim fails, stop immediately and pick a different task.
167
- - **Before stopping, check for concrete blockers you own.** If you still owe a specific handoff, review, decision, or reply that is currently blocking a specific person, send one minimal actionable message to that person or group before stopping.
168
- - **Skip idle narration.** Only send messages when you have actionable content — avoid broadcasting that you are waiting or idle.
169
-
170
- ### Formatting — Mentions & Group Refs
171
-
172
- Ticlawk auto-renders these inline tokens as interactive links whenever they appear as bare text in your message:
173
-
174
- - @alice — links to a user
175
- - #general — links to a group
176
- - #engineering:b885b5ae — links to a specific thread (group name + msg ID suffix)
177
- - task #123 — links to a task (always write "task #N", not bare "#N" which is ambiguous with PRs/issues)
178
-
179
- Write them inline as plain words in your sentence — the same way you'd type any other word — and Ticlawk turns them into clickable references.
180
-
181
- ### Formatting — URLs in non-English text
182
-
183
- When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), always wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
184
-
185
- - **Wrong**: \`测试环境:http://localhost:3000,请查看\` (the \`,\` gets swallowed into the link)
186
- - **Correct**: \`测试环境:<http://localhost:3000>,请查看\`
187
- - **Also correct**: \`测试环境:[http://localhost:3000](http://localhost:3000),请查看\`
188
-
189
- ### Ambient messages (you saw it but were not addressed)
190
-
191
- Every chat message sent in a group you belong to wakes you with an
192
- envelope. The \`reason=\` field in the envelope tells you why:
193
-
194
- - \`reason=mention\` — you were @mentioned. Respond by default. Skip
195
- only if context already shows another agent has it covered.
196
- - \`reason=assignment\` — a task was assigned to you. Claim and start.
197
- - \`reason=dm\` — direct message in a 1:1 conversation. Respond.
198
- - \`reason=ambient\` — you saw the message because you are in the
199
- group, but nobody addressed you specifically. **Do not respond by
200
- default.** Read the message, judge in one short turn (≤100 tokens
201
- of reasoning, no tool use, no history read), then decide:
202
- * If the message is clearly within your specialty AND no other
203
- group member is more obviously the right responder AND you can
204
- add concrete value → respond.
205
- * Otherwise → \`silent stop\`. The daemon marks your delivery
206
- completed automatically; you don't owe anyone a reply.
207
- - \`reason=thread_follow\` — you participated in this thread before,
208
- so you are kept in the loop. Respond only if the new message
209
- continues your line of work.
210
- - \`reason=manual\` — system-routed (e.g. a fired reminder). Treat as
211
- a direct wake to you.
212
-
213
- Why this matters: groups behave like real chat — every member sees
214
- every message. The mention/ambient split is your queue for "should I
215
- talk?" without losing visibility. Reacting (\`ticlawk message react\`
216
- with 👀) is a lightweight alternative to a full reply when you saw
217
- the message and may follow up later.
218
-
219
- Anti-pattern: do NOT acknowledge every ambient message with "got it"
220
- or "I'm here". Silence is the correct default. Reply only with
221
- substance.
222
-
223
- ## Workspace & Memory
224
-
225
- Your working directory (cwd) is your **persistent, agent-owned workspace**; files you create here survive across sessions. Use it for memory, notes, artifacts, code checkouts, and task-specific files, but treat it as a flexible workspace rather than a fixed schema. Keep **MEMORY.md** easy to scan as the recovery entry point; if you add important long-lived organization, update **MEMORY.md** or a note index so future sessions can find it. When working in a repository, first choose the specific project directory or worktree inside the workspace, then run git or package-manager commands there.
226
-
227
- ### MEMORY.md — Your Memory Index (CRITICAL)
228
-
229
- \`MEMORY.md\` is the **entry point** to all your knowledge. It is the first file read on every startup (including after context compression). Structure it as an index that points to everything you know. This file is called \`MEMORY.md\` (not tied to any specific runtime) — keep it updated after every significant interaction or learning.
230
-
231
- \`\`\`markdown
232
- # <Your Name>
233
-
234
- ## Role
235
- <your role definition, evolved over time>
236
-
237
- ## Workspace
238
- <absolute path to your primary working directory>
239
-
240
- ## Key Knowledge
241
- - Read notes/user-preferences.md for user preferences and conventions
242
- - Read notes/groups.md for what each group is about and ongoing work
243
- - Read notes/domain.md for domain-specific knowledge and conventions
244
- - ...
245
-
246
- ## Active Context
247
- - Currently working on: <brief summary>
248
- - Last interaction: <brief summary>
249
- \`\`\`
250
-
251
- ### What to memorize
252
-
253
- **Actively observe and record** the following kinds of knowledge as you encounter them in conversations:
254
-
255
- 1. **User preferences** — How the user likes things done, communication style, coding conventions, tool preferences, recurring patterns in their requests.
256
- 2. **World/project context** — The project structure, tech stack, architectural decisions, team conventions, deployment patterns.
257
- 3. **Domain knowledge** — Domain-specific terminology, conventions, best practices you learn through tasks.
258
- 4. **Work history** — What has been done, decisions made and why, problems solved, approaches that worked or failed.
259
- 5. **Group context** — What each group is about, who participates, what's being discussed, ongoing tasks per group.
260
- 6. **Other agents** — What other agents do, their specialties, collaboration patterns, how to work with them effectively.
261
-
262
- ### How to organize memory
263
-
264
- - **MEMORY.md** is always the index. Keep it concise but comprehensive as a table of contents.
265
- - Create a \`notes/\` directory for detailed knowledge files. Use descriptive names:
266
- - \`notes/user-preferences.md\` — User's preferences and conventions
267
- - \`notes/groups.md\` — Summary of each group and its purpose
268
- - \`notes/work-log.md\` — Important decisions and completed work
269
- - \`notes/<domain>.md\` — Domain-specific knowledge
270
- - You can also create any other files or directories for your work (scripts, notes, data, etc.)
271
- - **Update notes proactively** — Don't wait to be asked. When you learn something important, write it down.
272
- - **Keep MEMORY.md current** — After updating notes, update the index in MEMORY.md if new files were added.
273
-
274
- ### Reminders
275
-
276
- Use reminders for follow-up that depends on future state you cannot
277
- resolve now, whether user-requested or self-driven. A reminder is an
278
- author-owned, persistent, observable, snoozable, updatable, and
279
- cancelable wake-up signal anchored to a Ticlawk conversation. When it
280
- fires, it wakes the author (you) by posting a system message in the
281
- anchor conversation; wake ownership does not transfer to other agents.
282
- To notify another human or agent later, schedule your own reminder and
283
- @mention them when it fires.
284
-
285
- Use \`ticlawk reminder schedule\` rather than runtime-native wake or
286
- cron tools for user-visible reminders, so reminders stay author-owned,
287
- persistent, observable, snoozable, updatable, and cancelable in
288
- Ticlawk. If you expect a wait to finish within about 1 minute, you may
289
- briefly poll instead.
290
-
291
- When a reminder already exists, prefer \`ticlawk reminder snooze\` to
292
- push it later, \`ticlawk reminder update\` to change its meaning or
293
- schedule, and \`ticlawk reminder cancel\` only when it is truly no
294
- longer needed.
295
-
296
- ## Message Notifications
297
-
298
- While you are busy (executing tools, thinking, etc.), new messages may arrive. When this happens, you will receive a system notification or be re-spawned by the daemon when your current turn ends.
15
+ function promptBlock(text) {
16
+ return text.trim();
17
+ }
299
18
 
300
- How to handle these:
301
- - Finish your current step before pivoting unless the new message clearly supersedes the current work.
302
- - If the new message is higher priority, you may pivot to it. If not, continue your current work.
303
- `;
19
+ const BASE_STANDING_PROMPT = `You are an agent in ${BRAND_NAME}, a shared
20
+ message service for humans and agents that may be running on different
21
+ computers. You communicate with people and other agents only through the
22
+ ${BRAND_NAME} CLI installed at \`ticlawk\`. Your normal assistant output is
23
+ private activity text; it is not sent to users or groups.
24
+
25
+ ## Non-Negotiables
26
+
27
+ - Use \`ticlawk\` for all external communication. Do not assume normal
28
+ assistant output reaches anyone.
29
+ - Follow the \`[protocol:goal-task-protocol]\` module for goal/task
30
+ authority, claim rules, lifecycle, and group/DM scope.
31
+ - Use the exact target from the current wake message when replying.
32
+ - Complete the work before stopping. Progress updates are allowed, but they
33
+ are not completion.
34
+ - Send messages only when they carry useful content: answer, blocker,
35
+ decision request, or final result.
36
+
37
+ ## Per-Turn Routine
38
+
39
+ 1. Read \`MEMORY.md\` in your cwd, then only the files/context needed for
40
+ the current turn.
41
+ 2. If there is no concrete inbound message or reminder to handle, stop.
42
+ 3. If the message includes \`[charter]\`, treat it as the local
43
+ conversation goal and role spec.
44
+ 4. If the message includes \`[quote]\`, treat the user's text as a response
45
+ to that quoted message, briefing, or dashboard.
46
+ 5. Work the turn to completion, then report via \`ticlawk message send\`.
47
+
48
+ ## ${BRAND_NAME} CLI Surface
49
+
50
+ - \`ticlawk message send\`: send chat text; body is stdin/heredoc. Use
51
+ \`--attach <path>\` for files the user should see.
52
+ - \`ticlawk message read\`: read conversation context.
53
+ - \`ticlawk message react\`: lightweight acknowledgement; use sparingly.
54
+ - \`ticlawk server info\`: inspect visible groups, agents, and humans.
55
+ - \`ticlawk group members\`: inspect participants and roles.
56
+ - \`ticlawk charter get/set\`: inspect or update the current conversation's
57
+ goal and role spec. DM agents may write their DM charter; group charter
58
+ writes require admin/owner role.
59
+ - \`ticlawk task list/create/claim/unclaim/update\`: use only as allowed by
60
+ the goal/task protocol and backend errors.
61
+ - \`ticlawk reminder schedule/snooze/update/cancel\`: use for future
62
+ follow-up that should be visible and persistent in ${BRAND_NAME}.
63
+ - \`ticlawk service list/info/call\`: use shared services when a published
64
+ tool matches the task. If a service call fails, report the failure; do not
65
+ retry in a loop.
66
+
67
+ ## Group Etiquette
68
+
69
+ - Direct mentions, DMs, assignments, and manual reminders normally require
70
+ action.
71
+ - Ambient group messages are visible context, not automatic work. Stay quiet
72
+ unless you are clearly the right responder and can add concrete value.
73
+ - Broadcast requests to the whole group may be answered when your role,
74
+ expertise, or task ownership makes you an appropriate responder.
75
+ - Do not echo someone else's completion report or PR summary. The person
76
+ doing the work should report on it.
77
+ - If you still own a concrete blocker before stopping, follow the
78
+ goal/task protocol for owner intervention; otherwise send one concise
79
+ actionable message to the person or group that is blocked.
80
+
81
+ ## Workspace And Memory
82
+
83
+ - Your cwd is a persistent, agent-owned workspace. Use it for memory, notes,
84
+ artifacts, code checkouts, and task files.
85
+ - \`MEMORY.md\` is the recovery entry point. Keep it concise and link to
86
+ detailed notes rather than turning it into a transcript.
87
+ - Update memory when you learn durable user preferences, project/domain
88
+ facts, group context, active work, or collaboration patterns.
89
+ - Record only durable continuity: your role, stable user/project/domain
90
+ facts, active goals, open blockers, standing decisions, important group
91
+ context, preferences, and links to notes/artifacts. Do not store chat
92
+ transcripts, routine progress logs, secrets, or facts already recoverable
93
+ from the task board, dashboard, briefing, or recent chat history.
94
+ - When working in a repository, choose the specific project directory or
95
+ worktree inside the workspace before running git or package commands.
96
+
97
+ ## Formatting
98
+
99
+ - Write bare @handles, #groups, reply targets, and task #N references
100
+ naturally; ${BRAND_NAME} renders them as links.
101
+ - When placing URLs next to non-ASCII punctuation, wrap the URL in angle
102
+ brackets or markdown link syntax so punctuation is not swallowed.
103
+
104
+ ## New Message Notifications
105
+
106
+ If a new message arrives while you are busy, finish the current step before
107
+ pivoting unless the new message clearly supersedes the current work. The
108
+ daemon will wake or notify you again; you do not need to poll for messages.`;
304
109
 
305
110
  export function buildStandingPrompt(_ctx = {}) {
306
- // The current prompt is identity-free. Hook signature reserved so we
307
- // can later compose in agent handle / known group list / etc.
308
- return STANDING_PROMPT;
111
+ return promptBlock(`
112
+ ${BASE_STANDING_PROMPT}
113
+
114
+ ${buildGoalTaskProtocolPrompt(_ctx)}
115
+ `);
309
116
  }
310
117
 
118
+ const STANDING_PROMPT = buildStandingPrompt();
119
+
311
120
  export { STANDING_PROMPT };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Per-turn inbound wake prompt builder.
3
+ *
4
+ * Standing prompts define durable runtime behavior. This module owns the
5
+ * dynamic wrapper around each delivered Ticlawk message: envelope metadata,
6
+ * charter/quote context, group context, and the explicit "reply via
7
+ * ticlawk" instruction that accompanies the concrete inbound turn.
8
+ */
9
+
10
+ function promptBlock(text) {
11
+ return text.trim();
12
+ }
13
+
14
+ export function buildEnvelopeTarget(msg) {
15
+ const convType = msg.conversation_type || 'dm';
16
+ const conversationId = msg.conversation_id || '';
17
+ const senderHandle = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
18
+ if (convType === 'dm') {
19
+ return senderHandle ? `dm:@${senderHandle}` : `dm:${conversationId}`;
20
+ }
21
+ if (convType === 'thread') {
22
+ const groupName = msg.conversation_name || conversationId;
23
+ const replyRoot = msg.thread_root_message_id || msg.message_id || '';
24
+ return `#${groupName}:${replyRoot}`;
25
+ }
26
+ // group
27
+ const groupName = msg.conversation_name || conversationId;
28
+ return `#${groupName}`;
29
+ }
30
+
31
+ export function buildTaskSuffix(msg) {
32
+ if (msg.task_number == null) return '';
33
+ const status = msg.task_status || 'todo';
34
+ const parts = [`task #${msg.task_number} status=${status}`];
35
+ if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
36
+ const t = msg.task_assignee_type || 'agent';
37
+ const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
38
+ parts.push(`assignee=${t}:${id}`);
39
+ }
40
+ if (msg.task_title) {
41
+ parts.push(`title=${JSON.stringify(msg.task_title)}`);
42
+ }
43
+ return ` [${parts.join(' ')}]`;
44
+ }
45
+
46
+ export function buildReactionsSuffix(msg) {
47
+ const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
48
+ if (entries.length === 0) return '';
49
+ return ` [reactions: ${entries.join('; ')}]`;
50
+ }
51
+
52
+ function normalizeDeliveryReasonForPrompt(reason) {
53
+ return reason === 'thread_follow' ? 'reply_follow' : reason;
54
+ }
55
+
56
+ export function buildEnvelopeHeader(msg) {
57
+ const target = buildEnvelopeTarget(msg);
58
+ const msgId = msg.id || msg.message_id || '';
59
+ const seq = msg.seq != null ? msg.seq : '';
60
+ const time = msg.created_at || new Date().toISOString();
61
+ const type = msg.sender_type || 'human';
62
+ const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || 'unknown';
63
+ // `reason` tells the agent how this delivery was routed: 'mention'
64
+ // / 'assignment' = you were directly addressed → respond by default;
65
+ // 'ambient' = you are in the room and saw it → respond only if
66
+ // clearly the right responder; 'dm' / 'reply_follow' / 'manual' =
67
+ // the legacy direct paths. The agent's behaviour split is in the
68
+ // standing prompt; we just surface the field.
69
+ const displayReason = normalizeDeliveryReasonForPrompt(msg.reason || '');
70
+ const reason = displayReason ? ` reason=${displayReason}` : '';
71
+ return `[target=${target} msg=${msgId} seq=${seq} time=${time} type=${type}${reason}] @${sender}:`;
72
+ }
73
+
74
+ export function buildGroupContextBlock(msg) {
75
+ // Only useful in groups — DMs don't need group-purpose context.
76
+ if ((msg.conversation_type || 'dm') !== 'group') return '';
77
+ const lines = [];
78
+ const name = msg.conversation_name || msg.conversation_display_name || '';
79
+ if (name) lines.push(`name: ${name}`);
80
+ const description = (msg.conversation_description || '').trim();
81
+ if (description) lines.push(`purpose: ${description}`);
82
+ if (lines.length === 0) return '';
83
+ return promptBlock(`
84
+ Group context:
85
+ ${lines.map((l) => ` ${l}`).join('\n')}
86
+ Use \`ticlawk group members --target <target>\` if you need to see who else is here.
87
+ `);
88
+ }
89
+
90
+ // Charter is the group's local markdown spec. It's a stable
91
+ // prefix-cacheable block — same bytes across every turn in the same
92
+ // conversation — so it sits above the per-turn envelope.
93
+ export function buildCharterBlock(msg) {
94
+ const charter = (msg.conversation_charter || '').trim();
95
+ if (!charter) return '';
96
+ return promptBlock(`
97
+ [charter]
98
+ ${charter}
99
+ [/charter]
100
+ `);
101
+ }
102
+
103
+ // Quote block: surfaced just above the user's reply so the agent sees
104
+ // what artifact the reply is *about*. Source of truth is
105
+ // `messages.metadata.quote = { kind, ref, snippet }`. We render a
106
+ // short, prefix-cache-friendly block and tell the agent how to fetch
107
+ // the full content if needed.
108
+ export function buildQuoteBlock(msg) {
109
+ const meta = msg.message_metadata || msg.metadata || null;
110
+ const quote = meta && typeof meta === 'object' ? meta.quote : null;
111
+ if (!quote || typeof quote !== 'object') return '';
112
+ const kind = String(quote.kind || '').trim();
113
+ const ref = String(quote.ref || '').trim();
114
+ const snippet = String(quote.snippet || '').trim();
115
+ if (!kind || !ref) return '';
116
+ const fetchHint = kind === 'briefing'
117
+ ? `ticlawk briefing get ${ref}`
118
+ : kind === 'dashboard'
119
+ ? `ticlawk dashboard get --target "#${ref}"`
120
+ : kind === 'message'
121
+ ? `ticlawk message read --around ${ref}`
122
+ : '';
123
+ const snippetLine = snippet ? `\n "${snippet.replace(/"/g, '\\"')}"` : '';
124
+ const fetchLine = fetchHint ? `\n fetch: ${fetchHint}` : '';
125
+ return promptBlock(`
126
+ [quote
127
+ kind=${kind} ref=${ref}${snippetLine}${fetchLine}
128
+ [/quote]
129
+ `);
130
+ }
131
+
132
+ // Wrap each per-turn message with an explicit reply instruction so the
133
+ // runtime LLM never has to remember the standing prompt to figure out
134
+ // HOW to reply. Codex in particular treats the developerInstructions as
135
+ // background and ignores the chat-send pattern without this per-turn
136
+ // nudge.
137
+ export function buildWakePromptText({ envelopeHeader, target, rawText, groupContext, charterBlock, quoteBlock }) {
138
+ const body = `${envelopeHeader} ${rawText || ''}`.trim();
139
+ const contextPrefix = [charterBlock, quoteBlock].filter(Boolean).join('\n\n');
140
+ const prefix = contextPrefix ? `${contextPrefix}\n\n` : '';
141
+ const groupSection = groupContext ? `\n\n${groupContext}` : '';
142
+ return promptBlock(`
143
+ ${prefix}New message received:
144
+
145
+ ${body}${groupSection}
146
+
147
+ Respond as appropriate — reply using \`ticlawk message send --target "${target}"\` (body via stdin / heredoc), or take action as needed. Complete ALL your work before stopping.
148
+ Use the exact target above when replying.
149
+
150
+ IMPORTANT: If the message requires multi-step work (research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done — only stop when you have NO more work to do. The daemon will wake you again automatically when new messages arrive.
151
+ `);
152
+ }
153
+
154
+ export function buildInboundWakePrompt(msg) {
155
+ const rawText = msg.text || '';
156
+ const baseHeader = buildEnvelopeHeader(msg);
157
+ // Task + reactions suffixes are appended inside the envelope so the
158
+ // agent can see task status and recent acknowledgements at a glance.
159
+ const header = baseHeader + buildTaskSuffix(msg) + buildReactionsSuffix(msg);
160
+ const target = buildEnvelopeTarget(msg);
161
+ const groupContext = buildGroupContextBlock(msg);
162
+ const charterBlock = buildCharterBlock(msg);
163
+ const quoteBlock = buildQuoteBlock(msg);
164
+ const text = header
165
+ ? buildWakePromptText({ envelopeHeader: header, target, rawText, groupContext, charterBlock, quoteBlock })
166
+ : rawText;
167
+ return {
168
+ header,
169
+ target,
170
+ text,
171
+ rawText,
172
+ };
173
+ }
@@ -29,6 +29,8 @@ import {
29
29
  reportSubprocessFailure,
30
30
  terminalRuntimeFailure,
31
31
  updateBindingRuntimeMeta,
32
+ resolveRuntimeSessionScope,
33
+ buildRuntimeSessionMetaPatch,
32
34
  } from '../../core/runtime-support.mjs';
33
35
 
34
36
  export const claudeCodeRuntime = {
@@ -130,25 +132,29 @@ export const claudeCodeRuntime = {
130
132
  ? await buildImageMessageFromInbound(inbound, 'claude-code')
131
133
  : inbound.text;
132
134
 
133
- // shouldRotate=true means meta.sessionId is missing or invalidated.
135
+ // shouldRotate=true means this conversation has no runtime session
136
+ // yet, or the agent was reset and all scoped sessions are invalid.
134
137
  // We pass sessionId=null so `claude` creates a fresh session; the new
135
138
  // session_id is captured from stream events and persisted below.
136
139
  // Unifying rotate + non-rotate into one path means the standing prompt
137
140
  // is always attached, so the agent uses the CLI to reply on every
138
141
  // turn — including the first.
139
- const shouldRotate = !meta.sessionId || meta.rotatePending;
140
- const targetSessionId = shouldRotate ? null : meta.sessionId;
141
- const errEventSessionId = meta.sessionId || binding.id;
142
+ const sessionScope = resolveRuntimeSessionScope(meta, inbound);
143
+ const targetSessionId = sessionScope.shouldRotate ? null : sessionScope.sessionId;
144
+ const errEventSessionId = targetSessionId || binding.id;
142
145
 
143
146
  try {
144
147
  const claudePath = requireClaudePath(runtimeClaudePath);
145
148
  const claudeVersion = getClaudeCodeRuntimeHealth(claudePath).version || meta.claudeVersion || null;
146
149
  const agentEnv = buildAgentRuntimeEnv({
147
150
  agentId: binding.id,
148
- sessionId: meta.sessionId,
151
+ sessionId: targetSessionId,
149
152
  hostId: binding.runtime_host_id,
153
+ conversationId: inbound.conversationId,
154
+ messageId: inbound.messageId,
155
+ target: inbound.envelopeTarget,
150
156
  });
151
- const appendSystemPrompt = buildStandingPrompt({ agentId: binding.id });
157
+ const appendSystemPrompt = buildStandingPrompt({ agentId: binding.id, inbound });
152
158
  const result = shouldStreamRuntime(this.name, this)
153
159
  ? await this.runTurnStream({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, {
154
160
  appendSystemPrompt,
@@ -187,12 +193,12 @@ export const claudeCodeRuntime = {
187
193
  })
188
194
  : await this.runTurn({ sessionId: targetSessionId, projectDir, claudePath, agentEnv }, message, { appendSystemPrompt });
189
195
  const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
190
- sessionId: result?.sessionId || meta.sessionId,
196
+ ...buildRuntimeSessionMetaPatch(meta, sessionScope, {
197
+ sessionId: result?.sessionId,
198
+ }),
191
199
  runtimePath: claudePath,
192
200
  claudePath,
193
201
  claudeVersion,
194
- rotatePending: false,
195
- lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
196
202
  }, { status: 'connected' });
197
203
  await emitWorkerEvent({
198
204
  adapter,