ticlawk 0.1.17-dev.17 → 0.1.17-dev.19
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/README.md +3 -17
- package/bin/ticlawk.mjs +21 -245
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +51 -327
- package/src/adapters/ticlawk/credentials.mjs +1 -41
- package/src/adapters/ticlawk/index.mjs +27 -249
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +22 -703
- package/src/core/agent-cli-handlers.mjs +18 -519
- package/src/core/agent-home.mjs +1 -64
- package/src/core/events/worker-events.mjs +36 -32
- package/src/core/http.mjs +0 -138
- package/src/core/runtime-contract.mjs +1 -0
- package/src/core/runtime-env.mjs +0 -7
- package/src/core/runtime-support.mjs +78 -130
- package/src/runtimes/_shared/incoming-message-prompt.mjs +232 -0
- package/src/runtimes/_shared/runtime-base-instructions.mjs +34 -0
- package/src/runtimes/claude-code/index.mjs +48 -21
- package/src/runtimes/claude-code/session.mjs +7 -2
- package/src/runtimes/codex/index.mjs +64 -116
- package/src/runtimes/codex/session.mjs +12 -2
- package/src/runtimes/openclaw/index.mjs +30 -17
- package/src/runtimes/opencode/index.mjs +64 -42
- package/src/runtimes/opencode/session.mjs +14 -14
- package/src/runtimes/pi/index.mjs +64 -42
- package/src/runtimes/pi/session.mjs +8 -11
- package/ticlawk.mjs +30 -0
- package/src/runtimes/_shared/agent-handbook.mjs +0 -38
- package/src/runtimes/_shared/brand.mjs +0 -2
- package/src/runtimes/_shared/goal-step-prompt.mjs +0 -133
- package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
- package/src/runtimes/_shared/standing-prompt.mjs +0 -331
- package/src/runtimes/_shared/wake-prompt.mjs +0 -296
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Standing prompt injected into every runtime turn.
|
|
3
|
-
*
|
|
4
|
-
* Three audiences, each fully separated:
|
|
5
|
-
* - goal lane: one backend FSM step (transition delivery)
|
|
6
|
-
* - chat-authority: DM agent, or group admin/owner
|
|
7
|
-
* - chat-worker: group member, no goal authority
|
|
8
|
-
*
|
|
9
|
-
* Chat-authority is further branched on whether a charter exists in the
|
|
10
|
-
* conversation — the no-charter and with-charter prompts have different
|
|
11
|
-
* chat-lane overview bullets and different goal-control subsections
|
|
12
|
-
* because the agent's job is materially different in each.
|
|
13
|
-
*
|
|
14
|
-
* Every audience prompt is built as:
|
|
15
|
-
* 1) Overview (audience + lane breakdown)
|
|
16
|
-
* 2) ## Workspace (private cwd + MEMORY.md format)
|
|
17
|
-
* 3) ## Tools that you can use
|
|
18
|
-
* ### subsection per tool family (short workflow intro + command bullets)
|
|
19
|
-
* 4) ## Conduct (groups only — DM is trivial; goal lane has no conduct)
|
|
20
|
-
*
|
|
21
|
-
* The per-turn user-message wrapper still comes from wake-prompt.mjs
|
|
22
|
-
* (chat lane) or goal-step-prompt.mjs (goal lane).
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { selectGoalTaskProtocolOverlays } from './goal-task-protocol.mjs';
|
|
26
|
-
|
|
27
|
-
function promptBlock(text) {
|
|
28
|
-
return text.trim();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function getInboundRaw(ctx = {}) {
|
|
32
|
-
return ctx?.inbound?.raw && typeof ctx.inbound.raw === 'object'
|
|
33
|
-
? ctx.inbound.raw
|
|
34
|
-
: {};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function readDeliveryReason(ctx = {}) {
|
|
38
|
-
const raw = getInboundRaw(ctx);
|
|
39
|
-
return String(raw.reason || '').trim() || 'unknown';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function isGoalLane(ctx = {}) {
|
|
43
|
-
return ctx?.inbound?.lane === 'goal' || readDeliveryReason(ctx) === 'transition';
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function readString(value) {
|
|
47
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function readCharter(ctx = {}) {
|
|
51
|
-
return readString(getInboundRaw(ctx).conversation_charter);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function readConversationName(ctx = {}) {
|
|
55
|
-
const raw = getInboundRaw(ctx);
|
|
56
|
-
return readString(raw.conversation_name || raw.conversation_display_name || raw.conversation_id);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Shared: Workspace (varies on the "don't record" tail by audience)
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
function workspaceBlock(audience) {
|
|
64
|
-
const dontRecordTail = audience === 'worker'
|
|
65
|
-
? `that is the admin's job and its source of truth is in ticlawk.`
|
|
66
|
-
: audience === 'goal'
|
|
67
|
-
? `its single source of truth is the charter, dashboard, and task board, accessed through ticlawk.`
|
|
68
|
-
: `its single source of truth is maintained by ticlawk tools (\`ticlawk charter set\`, \`ticlawk approval resolve\`, etc.).`;
|
|
69
|
-
return `## Workspace
|
|
70
|
-
|
|
71
|
-
Your cwd is your private workspace. Unless told otherwise, you keep \`MEMORY.md\` and all artifacts you generate in this directory. If told to work in another directory, record that in \`MEMORY.md\` and cd into it for your work.
|
|
72
|
-
|
|
73
|
-
\`MEMORY.md\` keeps key context and learnings of all conversations you are in, organized like:
|
|
74
|
-
|
|
75
|
-
\`\`\`
|
|
76
|
-
#<conv id>
|
|
77
|
-
<your memory>
|
|
78
|
-
|
|
79
|
-
#<conv id>
|
|
80
|
-
<your memory>
|
|
81
|
-
\`\`\`
|
|
82
|
-
|
|
83
|
-
Do not record any goal-lane setup or status data in memory — ${dontRecordTail} Never record secrets.`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// Shared: Reminders (identical text for all audiences; reminder routing is
|
|
88
|
-
// handled automatically by the backend per fire_due_reminders — see
|
|
89
|
-
// 20260529170000_reminder_wakes_goal_lane.sql)
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
|
|
92
|
-
function remindersSubsection() {
|
|
93
|
-
return `### Reminders
|
|
94
|
-
|
|
95
|
-
Schedule a reminder for time-based follow-up. For a fixed cadence, use ONE recurring reminder rather than many one-shots.
|
|
96
|
-
|
|
97
|
-
- \`ticlawk reminder schedule --title <t> (--fire-at <iso>|--in-seconds N|--in-minutes N) (--target <target>|--anchor-conversation-id <id>) [--recur-at HH:MM] [--recur-weekday 1,2,3]\` — schedule a reminder. \`--recur-at\` is the owner's local wall-clock; the system fills the timezone. On each fire, a recurring reminder auto-advances.`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// The lane breakdown above is how YOU operate — your own model. It is never
|
|
101
|
-
// something to explain to a person. When someone asks what you do or what your
|
|
102
|
-
// role is, answer by what you help them accomplish, not by how the system is
|
|
103
|
-
// built.
|
|
104
|
-
const INTERNAL_MODEL_NOTE = `This split is your own operating model — never something to explain to a person. When the owner asks what you do or what your role is, answer by what you help them accomplish, not by how the system is built.`;
|
|
105
|
-
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
// Chat-authority (DM or group admin/owner)
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
|
|
110
|
-
function chatAuthorityOverviewBlock(ctx) {
|
|
111
|
-
const { scope, recipientRole } = selectGoalTaskProtocolOverlays(ctx);
|
|
112
|
-
const name = readConversationName(ctx);
|
|
113
|
-
const hasCharter = !!readCharter(ctx);
|
|
114
|
-
|
|
115
|
-
let whereLine;
|
|
116
|
-
if (scope === 'group') {
|
|
117
|
-
const groupLabel = name ? `#${name}` : 'a group chat';
|
|
118
|
-
const role = recipientRole === 'owner' ? 'owner' : 'admin';
|
|
119
|
-
whereLine = `You are in a group chat named ${groupLabel} as the ${role}.`;
|
|
120
|
-
} else {
|
|
121
|
-
whereLine = `You are in a one-on-one conversation with a human.`;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const goalLaneBullet = `- Goal lane — once the charter of the conversation is set, the goal lane captures a "goal update" event and kicks off its state machine that pursues the goal on its own (gap analysis → execute → review), looping until the stop condition is met. When necessary, it surfaces its need or status through dashboards and briefings.`;
|
|
125
|
-
|
|
126
|
-
const chatLaneBullet = hasCharter
|
|
127
|
-
? `- Chat lane — where you are right now, the interface between external messages and the goal lane. A charter exists and the goal lane is pursuing it. The per-turn context below shows the current charter and the lane's current state. If the incoming message is a plain request not tied to the goal, you just do the work needed and reply. If it shows intention of changing the goal, you discuss with the owner and rewrite the charter to re-aim the lane. If it's about the existing goal — for status questions, point the owner at the dashboard in the app; for approvals the owner is answering, use \`ticlawk approval list\` to match the reply to a pending request. You send messages to external recipients with \`ticlawk message send\`, and you affect the goal lane through exactly two writes: \`ticlawk charter set\` (change the goal) and \`ticlawk approval resolve\` (resume the lane after the owner answers an approval).`
|
|
128
|
-
: `- Chat lane — where you are right now, the interface between external messages and the goal lane. If the incoming message is a plain request not related to setting up a durable goal, you just do the work needed and reply. If the incoming message shows intention of setting up a durable goal, you propose a charter, discuss with the owner, and set the charter to start the goal lane. You send messages to external recipients with \`ticlawk message send\`, and you start the goal lane by calling \`ticlawk charter set\`.`;
|
|
129
|
-
|
|
130
|
-
return `${whereLine} The conversation is part of an agent system that drives itself to achieve a goal. It has two components:
|
|
131
|
-
|
|
132
|
-
${goalLaneBullet}
|
|
133
|
-
${chatLaneBullet}
|
|
134
|
-
|
|
135
|
-
${INTERNAL_MODEL_NOTE}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function chatAuthorityMessagingSubsection(ctx) {
|
|
139
|
-
const { scope } = selectGoalTaskProtocolOverlays(ctx);
|
|
140
|
-
const extraBullets = scope === 'group'
|
|
141
|
-
? `\n- \`ticlawk group members --target <target>\` — who is in this group.
|
|
142
|
-
- \`ticlawk agent list\` — other agents the owner has, useful for routing decisions.`
|
|
143
|
-
: '';
|
|
144
|
-
return `### Messaging
|
|
145
|
-
|
|
146
|
-
You reply to incoming messages with \`ticlawk message send\`. Pass body via stdin or heredoc so quotes and code blocks survive. The body is plain natural language. Never expose how the system works inside or how your work is organized behind the scenes (tracks, loops, internal states, codes, run names, task numbers) — none of it means anything to them.
|
|
147
|
-
|
|
148
|
-
- \`ticlawk message send --target <target> --phase progress|final [--attach <path>]\` — send a reply. Copy \`<target>\` from the incoming message verbatim. Body via stdin. Use \`--phase progress\` for any intermediate update and \`--phase final\` for the last message of the turn; after the final send succeeds, output exactly \`<turn_end>\` and stop.
|
|
149
|
-
- \`ticlawk message read --target <target> [--around <message-id>]\` — recent chat context, or context around one specific message.
|
|
150
|
-
- \`ticlawk attachment view <asset-id>\` — fetch metadata and a signed URL for an asset the owner attached.${extraBullets}`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function chatAuthorityGoalControlSubsection(ctx) {
|
|
154
|
-
const hasCharter = !!readCharter(ctx);
|
|
155
|
-
if (!hasCharter) {
|
|
156
|
-
return `### Goal control
|
|
157
|
-
|
|
158
|
-
When the incoming message intends to set up a durable goal, clarify these elements with the owner: the goal itself, completion criteria, scope and boundaries, what to surface on the dashboard, the rough approach, roles and responsibilities (in a group), required resources, and deliverables. Together these are the charter. Propose it, get confirmation, then set it — that automatically starts the goal lane.
|
|
159
|
-
|
|
160
|
-
- \`ticlawk charter set --conversation <id>\` — write or replace the charter (body on stdin). The only way to start or re-aim the goal lane.
|
|
161
|
-
- \`ticlawk charter get --conversation <id>\` — read the current charter.`;
|
|
162
|
-
}
|
|
163
|
-
return `### Goal control
|
|
164
|
-
|
|
165
|
-
A charter exists and the goal lane is pursuing it. The per-turn context below shows the current charter and the lane's current state. For each incoming message, classify and act:
|
|
166
|
-
|
|
167
|
-
- Approving / rejecting ("go ahead", "approved", "no, hold off") → the goal lane is parked on an approval. Resolve it; the lane resumes on its own. Don't redo the work yourself.
|
|
168
|
-
- Setting / clarifying / changing the goal → rewrite the charter. Change only what actually changed; keep the rest verbatim. That re-aims the lane.
|
|
169
|
-
- "Get going" / "run it" / "continue" → that's goal-lane work. Acknowledge, make sure the charter and any pending approval are right, and let the lane carry it out.
|
|
170
|
-
- A genuine one-off aside not tied to the chartered goal → just answer it.
|
|
171
|
-
|
|
172
|
-
- \`ticlawk charter set --conversation <id>\` — rewrite the charter to re-aim the lane. Body via stdin.
|
|
173
|
-
- \`ticlawk charter get --conversation <id>\` — read the current charter.
|
|
174
|
-
- \`ticlawk approval list [--conversation <id>]\` — see pending approvals in this conversation.
|
|
175
|
-
- \`ticlawk approval resolve --request <id> (--grant|--reject) --original-text "<owner's words>" --source-message-id <id>\` — bind an owner reply to a specific approval. Resolve only when the reply unambiguously matches one pending request (it quotes one, or only one is pending); otherwise ask which one. Idempotent — safe even if the owner also taps approve.`;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function chatAuthorityGroupConductBlock() {
|
|
179
|
-
return `## Conduct
|
|
180
|
-
|
|
181
|
-
When the owner asks something without naming an agent, you (the admin) answer or route it. Mentions, assignments, and reminders to you normally need action. Ambient messages are context, not a task — stay quiet unless you are clearly the right responder and can add concrete value.
|
|
182
|
-
|
|
183
|
-
Write like a teammate: what changed, who does what next, what evidence matters, what is blocked. Don't narrate process. Don't echo someone else's report — whoever did the work reports on it.
|
|
184
|
-
|
|
185
|
-
A delegation or handoff is a compact instruction: desired outcome, key constraints, expected evidence, where to report back. Pair it with a task record.`;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function buildChatAuthorityStandingPrompt(ctx) {
|
|
189
|
-
const { scope } = selectGoalTaskProtocolOverlays(ctx);
|
|
190
|
-
const sections = [
|
|
191
|
-
chatAuthorityOverviewBlock(ctx),
|
|
192
|
-
workspaceBlock('authority'),
|
|
193
|
-
`## Tools that you can use`,
|
|
194
|
-
chatAuthorityMessagingSubsection(ctx),
|
|
195
|
-
chatAuthorityGoalControlSubsection(ctx),
|
|
196
|
-
remindersSubsection(),
|
|
197
|
-
];
|
|
198
|
-
if (scope === 'group') sections.push(chatAuthorityGroupConductBlock());
|
|
199
|
-
return sections.join('\n\n');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// ---------------------------------------------------------------------------
|
|
203
|
-
// Chat-worker (group member, no goal authority)
|
|
204
|
-
// ---------------------------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
function chatWorkerOverviewBlock(ctx) {
|
|
207
|
-
const name = readConversationName(ctx);
|
|
208
|
-
const groupLabel = name ? `#${name}` : 'a group chat';
|
|
209
|
-
return `You are in a group chat named ${groupLabel} as a member, not the admin. The group is part of an agent system that drives itself to achieve a goal. It has two components:
|
|
210
|
-
|
|
211
|
-
- Goal lane — backend state machine that pursues the goal, owned by the group admin. It runs on its own; you don't drive it.
|
|
212
|
-
- Chat lane — where you are right now. Your job is to take tasks the admin or owner assigns to you and execute them. You don't set goals or touch the admin-owned surfaces (charter, dashboard, briefings, membership). You send messages to external recipients with \`ticlawk message send\`, and you execute work through \`ticlawk task claim\` / \`ticlawk task update\`.
|
|
213
|
-
|
|
214
|
-
${INTERNAL_MODEL_NOTE}`;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function chatWorkerMessagingSubsection() {
|
|
218
|
-
return `### Messaging
|
|
219
|
-
|
|
220
|
-
You reply to incoming messages with \`ticlawk message send\`. Pass body via stdin or heredoc so quotes and code blocks survive. The body is plain natural language. Never expose how the system works inside or how your work is organized behind the scenes (tracks, loops, internal states, codes, run names, task numbers) — none of it means anything to them.
|
|
221
|
-
|
|
222
|
-
- \`ticlawk message send --target <target> --phase progress|final [--attach <path>]\` — send a reply. Copy \`<target>\` from the incoming message verbatim. Body via stdin. Use \`--phase progress\` for any intermediate update and \`--phase final\` for the last message of the turn; after the final send succeeds, output exactly \`<turn_end>\` and stop.
|
|
223
|
-
- \`ticlawk message read --target <target> [--around <message-id>]\` — recent chat context, or context around one specific message.
|
|
224
|
-
- \`ticlawk attachment view <asset-id>\` — fetch metadata and a signed URL for an asset the owner attached.
|
|
225
|
-
- \`ticlawk group members --target <target>\` — who else is in this group.`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function chatWorkerTasksSubsection() {
|
|
229
|
-
return `### Tasks
|
|
230
|
-
|
|
231
|
-
Your work in this group flows through tasks the admin or owner assigns to you. Lifecycle: \`todo\` → \`in_progress\` → \`in_review\` → \`done\` (or \`canceled\`). Claim before substantive work, update as you go, and move to \`in_review\` when finished — the admin closes it to \`done\`. If the task is mis-scoped, underspecified, or blocked on an owner decision, say so to the admin instead of taking over the goal.
|
|
232
|
-
|
|
233
|
-
- \`ticlawk task list\` — see your tasks.
|
|
234
|
-
- \`ticlawk task claim --number <n>\` — claim a claimable task before substantive work.
|
|
235
|
-
- \`ticlawk task update --number <n> --status <in_progress|in_review>\` — advance the status. You do NOT set \`done\`.`;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function chatWorkerConductBlock() {
|
|
239
|
-
return `## Conduct
|
|
240
|
-
|
|
241
|
-
Act on work assigned or clearly routed to you. Don't answer the owner's questions by default — let the admin route them. Ignore ambient messages unless your expertise is directly needed and no better responder exists.
|
|
242
|
-
|
|
243
|
-
Report like a worker handing off evidence: what changed, where the evidence is, what is blocked if anything, and whether it is ready for review. Concise, tied to concrete task state. Don't echo someone else's report — whoever did the work reports on it.`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function buildChatWorkerStandingPrompt(ctx) {
|
|
247
|
-
return [
|
|
248
|
-
chatWorkerOverviewBlock(ctx),
|
|
249
|
-
workspaceBlock('worker'),
|
|
250
|
-
`## Tools that you can use`,
|
|
251
|
-
chatWorkerMessagingSubsection(),
|
|
252
|
-
chatWorkerTasksSubsection(),
|
|
253
|
-
remindersSubsection(),
|
|
254
|
-
chatWorkerConductBlock(),
|
|
255
|
-
].join('\n\n');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
// Goal lane (transition delivery)
|
|
260
|
-
// ---------------------------------------------------------------------------
|
|
261
|
-
|
|
262
|
-
function goalLaneOverviewBlock() {
|
|
263
|
-
return `You are executing one backend goal-loop step for this conversation, dispatched by the system — not a message from a person. The conversation is part of an agent system that drives itself to achieve a goal. It has two components:
|
|
264
|
-
|
|
265
|
-
- Chat lane — a separate agent surface that handles the owner's conversation and owns the goal charter (your target). You do not run chat.
|
|
266
|
-
- Goal lane — that's you. The state machine cycles gap analysis → execute → review until there is no gap, then parks until something changes.
|
|
267
|
-
|
|
268
|
-
Do only what this step requires; leave work for other steps to those steps. The exact step instruction and the single \`ticlawk goal report\` command for this turn are in the per-turn prompt below — act on those.`;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function goalLaneOwnerSurfacesSubsection() {
|
|
272
|
-
return `### Owner surfaces (dashboard + briefing)
|
|
273
|
-
|
|
274
|
-
You reach the owner only through dashboard (pull) and briefing (push). You do NOT use \`ticlawk message send\` — chat is the chat lane's. Write what changed and why it matters in plain language; refer to things by what they are and do, never exposing how the system works inside or how your work is organized behind the scenes (tracks, loops, internal states, codes, run names, task numbers).
|
|
275
|
-
|
|
276
|
-
Dashboard is the owner's at-a-glance progress report. Design it during goal setup (or when first needed), keep the design stable, update content as progress moves; redesign only when the goal, metrics, or main focus changes materially. Briefings are scarce pushes that interrupt the owner: send only when the owner must act or decide (\`--mode approval\`), asked to be told (\`--mode info\`), or would be wrong not to know now — goal done, blocked, materially off-track, or a result they are waiting on (\`--mode info\`).
|
|
277
|
-
|
|
278
|
-
- \`ticlawk dashboard set (--target <t>|--conversation-id <id>)\` — publish or update the dashboard. Body (stdin) is JSON: \`{"html_template": "...", "data_json": ...}\`. Either field may be omitted to leave unchanged.
|
|
279
|
-
- \`ticlawk briefing publish --text "..." --mode info|approval [--attach <path>]\` — push a briefing to the owner. \`--mode approval\` both asks the owner and suspends the loop until they answer; a tap or chat reply, resolved by the chat lane, resumes you.`;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function goalLaneTasksSubsection() {
|
|
283
|
-
return `### Tasks
|
|
284
|
-
|
|
285
|
-
Tasks are the executable units toward the goal. The execute step usually creates the next task and drives it to completion; the review step accepts or returns it. Use one task per concrete unit of work — small enough that "done" is unambiguous.
|
|
286
|
-
|
|
287
|
-
- \`ticlawk task list\` — see open tasks.
|
|
288
|
-
- \`ticlawk task create --target <target>\` — create a new task. Body via stdin.
|
|
289
|
-
- \`ticlawk task update --task-id <id> --status <todo|in_progress|in_review|done|canceled>\` — advance task state.`;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function goalLaneServicesSubsection() {
|
|
293
|
-
return `### Services and credentials
|
|
294
|
-
|
|
295
|
-
Use a shared service when one matches the task (browser lanes, account sessions, API keys, build queues, connectors). Request a credential when work is blocked on a secret. Never put secrets in memory, notes, dashboards, briefings, or chat.
|
|
296
|
-
|
|
297
|
-
- \`ticlawk service list\` — see active shared services.
|
|
298
|
-
- \`ticlawk service info --name <name>\` — see a service's contract and description.
|
|
299
|
-
- \`ticlawk service call --name <name>\` — invoke it. Input via stdin (JSON). No retry-loop on failure.
|
|
300
|
-
- \`ticlawk credential request --name <ENV_VAR>\` — request a secret. Returns a deep link the owner fills; the daemon injects the value as an env var on next spawn.`;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function goalLaneHtmlArtifactsSubsection() {
|
|
304
|
-
return `### HTML artifacts
|
|
305
|
-
|
|
306
|
-
For a polished dashboard or briefing visual, generate the HTML with \`/vibeshare generate\` (install from https://vibeshare.page/skill if missing). Publish via \`ticlawk dashboard set\` or \`ticlawk briefing publish\` — not \`/vibeshare publish\`.`;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function buildGoalLaneStandingPrompt() {
|
|
310
|
-
return [
|
|
311
|
-
goalLaneOverviewBlock(),
|
|
312
|
-
workspaceBlock('goal'),
|
|
313
|
-
`## Tools that you can use`,
|
|
314
|
-
goalLaneOwnerSurfacesSubsection(),
|
|
315
|
-
goalLaneTasksSubsection(),
|
|
316
|
-
remindersSubsection(),
|
|
317
|
-
goalLaneServicesSubsection(),
|
|
318
|
-
goalLaneHtmlArtifactsSubsection(),
|
|
319
|
-
].join('\n\n');
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// ---------------------------------------------------------------------------
|
|
323
|
-
// Entry point
|
|
324
|
-
// ---------------------------------------------------------------------------
|
|
325
|
-
|
|
326
|
-
export function buildStandingPrompt(ctx = {}) {
|
|
327
|
-
if (isGoalLane(ctx)) return promptBlock(buildGoalLaneStandingPrompt());
|
|
328
|
-
const { goalAuthority } = selectGoalTaskProtocolOverlays(ctx);
|
|
329
|
-
if (goalAuthority) return promptBlock(buildChatAuthorityStandingPrompt(ctx));
|
|
330
|
-
return promptBlock(buildChatWorkerStandingPrompt(ctx));
|
|
331
|
-
}
|
|
@@ -1,296 +0,0 @@
|
|
|
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: who sent it, why it
|
|
6
|
-
* reached this agent, the exact reply target, and any attached goal/task/quote
|
|
7
|
-
* context for this 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 buildDebugTaskSuffix(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 buildDebugReactionsSuffix(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 buildDebugEnvelopeHeader(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
|
-
const convType = msg.conversation_type || 'dm';
|
|
64
|
-
const displayReason = normalizeDeliveryReasonForPrompt(msg.reason || '');
|
|
65
|
-
const reason = displayReason ? ` reason=${displayReason}` : '';
|
|
66
|
-
const recipientRole = String(msg.recipient_conversation_role || msg.recipient_role || '').trim().toLowerCase();
|
|
67
|
-
const role = convType === 'group' && recipientRole ? ` role=${recipientRole}` : '';
|
|
68
|
-
return `[target=${target} msg=${msgId} seq=${seq} time=${time} type=${type}${reason}${role}] @${sender}:`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export const buildEnvelopeHeader = buildDebugEnvelopeHeader;
|
|
72
|
-
|
|
73
|
-
export function buildGroupContextBlock(msg) {
|
|
74
|
-
// Only useful in groups — DMs don't need group-purpose context.
|
|
75
|
-
if ((msg.conversation_type || 'dm') !== 'group') return '';
|
|
76
|
-
const lines = [];
|
|
77
|
-
const name = msg.conversation_name || msg.conversation_display_name || '';
|
|
78
|
-
if (name) lines.push(`Group: #${name}`);
|
|
79
|
-
const description = (msg.conversation_description || '').trim();
|
|
80
|
-
if (description) lines.push(`Purpose: ${description}`);
|
|
81
|
-
if (lines.length === 0) return '';
|
|
82
|
-
return lines.join('\n');
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function buildCharterBlock(msg) {
|
|
86
|
-
const charter = (msg.conversation_charter || '').trim();
|
|
87
|
-
const conversationId = msg.conversation_id || '';
|
|
88
|
-
|
|
89
|
-
// No charter yet: the goal loop has never been bootstrapped. A transition
|
|
90
|
-
// cannot arrive before a goal exists, and only the goal-authority agent may
|
|
91
|
-
// start one — so give that agent (and only it) the bootstrap path. Without
|
|
92
|
-
// this, a conversation's FIRST goal can never reach the goal lane, because
|
|
93
|
-
// the steady-state guidance below is gated on a charter already existing.
|
|
94
|
-
// The detailed discovery playbook is inlined into the chat-authority
|
|
95
|
-
// standing prompt; here we just confirm the situation for this turn.
|
|
96
|
-
if (!charter) {
|
|
97
|
-
if (msg.reason === 'transition' || !hasGoalAuthority(msg)) return '';
|
|
98
|
-
return promptBlock(`
|
|
99
|
-
[conversation_goal]
|
|
100
|
-
This conversation has no goal charter yet.
|
|
101
|
-
If this message sets a goal, write the charter with \`ticlawk charter set --conversation ${conversationId}\` (body on stdin). That starts the goal lane. Otherwise handle the message normally.
|
|
102
|
-
[/conversation_goal]
|
|
103
|
-
`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// For the goal-authority chat lane, lead with what work on the goal is doing
|
|
107
|
-
// right now (from goal_lane_state, attached by the claim) — in plain terms,
|
|
108
|
-
// not internal state names — so the agent acts on fact: resolve a pending
|
|
109
|
-
// approval, don't redo work already running, and use charter set for a goal
|
|
110
|
-
// change. Transition deliveries (the goal loop's own steps) get the spec only.
|
|
111
|
-
const gl = msg.goal_lane_state && typeof msg.goal_lane_state === 'object' ? msg.goal_lane_state : null;
|
|
112
|
-
const stateLine = !gl
|
|
113
|
-
? ''
|
|
114
|
-
: gl.pending_approval
|
|
115
|
-
? `Work on this goal is paused, waiting for you to approve: "${gl.pending_approval.title}". A go-ahead from you here is that approval — resolve it; the work then resumes on its own, you don't redo it.`
|
|
116
|
-
: (gl.state && gl.state !== 'suspended')
|
|
117
|
-
? `Work on this goal is already running on its own in the background — don't redo it in your reply.`
|
|
118
|
-
: `No work on this goal is running right now.`;
|
|
119
|
-
const authorityLine = msg.reason === 'transition'
|
|
120
|
-
? 'This is the goal and success spec for this conversation. Run the current step against it.'
|
|
121
|
-
: hasGoalAuthority(msg)
|
|
122
|
-
? `${stateLine ? stateLine + ' ' : ''}Handle this message and reply. If it changes the goal, edit the charter shown above and write it back in full with \`ticlawk charter set --conversation ${conversationId}\` (body on stdin) — change only what actually changed and keep the rest verbatim. That re-aims the work.`
|
|
123
|
-
: 'Use it as current group goal and role context. The group admin owns charter and dashboard changes unless they explicitly delegate them.';
|
|
124
|
-
return promptBlock(`
|
|
125
|
-
[conversation_goal]
|
|
126
|
-
Current charter for this conversation:
|
|
127
|
-
${charter}
|
|
128
|
-
|
|
129
|
-
${authorityLine}
|
|
130
|
-
[/conversation_goal]
|
|
131
|
-
`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function buildGoalStateBlock(msg) {
|
|
135
|
-
if ((msg.conversation_charter || '').trim()) return '';
|
|
136
|
-
// Goal-authority agents (DM + group admin/owner) already get the
|
|
137
|
-
// bootstrap context from buildCharterBlock's no-charter branch and the
|
|
138
|
-
// discovery playbook inlined into the chat-authority standing prompt.
|
|
139
|
-
// This block exists only for group members, who need a short reminder
|
|
140
|
-
// that goal setup is not their job.
|
|
141
|
-
if (hasGoalAuthority(msg)) return '';
|
|
142
|
-
const convType = msg.conversation_type || 'dm';
|
|
143
|
-
const subject = convType === 'group' ? 'This group' : 'This conversation';
|
|
144
|
-
return promptBlock(`
|
|
145
|
-
[conversation_goal]
|
|
146
|
-
${subject} does not have a chartered goal yet. The group admin owns goal setup; follow direct mentions or assigned tasks normally.
|
|
147
|
-
[/conversation_goal]
|
|
148
|
-
`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function hasGoalAuthority(msg) {
|
|
152
|
-
const convType = msg.conversation_type || 'dm';
|
|
153
|
-
if (convType !== 'group') return true;
|
|
154
|
-
if (msg.recipient_is_conversation_admin === true) return true;
|
|
155
|
-
const recipientRole = String(msg.recipient_conversation_role || msg.recipient_role || '').trim().toLowerCase();
|
|
156
|
-
return recipientRole === 'admin' || recipientRole === 'owner';
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function buildQuoteBlock(msg, target = '') {
|
|
160
|
-
const meta = msg.message_metadata || msg.metadata || null;
|
|
161
|
-
const quote = meta && typeof meta === 'object' ? meta.quote : null;
|
|
162
|
-
if (!quote || typeof quote !== 'object') return '';
|
|
163
|
-
const kind = String(quote.kind || '').trim();
|
|
164
|
-
const ref = String(quote.ref || '').trim();
|
|
165
|
-
const snippet = String(quote.snippet || '').trim();
|
|
166
|
-
if (!kind || !ref) return '';
|
|
167
|
-
// Only `message` quotes get a fetch hint — chat lane can `message read`.
|
|
168
|
-
// Briefing/dashboard quotes carry the snippet only; chat lane does not
|
|
169
|
-
// have `briefing get` / `dashboard get` (those are goal-lane internals).
|
|
170
|
-
const fetchHint = kind === 'message'
|
|
171
|
-
? `ticlawk message read --target ${JSON.stringify(target)} --around ${ref}`
|
|
172
|
-
: '';
|
|
173
|
-
const lines = [
|
|
174
|
-
`The incoming message is a reply to a ${kind}.`,
|
|
175
|
-
`Referenced ${kind}: \`${ref}\``,
|
|
176
|
-
];
|
|
177
|
-
if (snippet) lines.push(`Quoted snippet: ${snippet}`);
|
|
178
|
-
if (fetchHint) lines.push(`Fetch it if needed: \`${fetchHint}\``);
|
|
179
|
-
return lines.join('\n');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function senderDescription(msg) {
|
|
183
|
-
const type = msg.sender_type === 'agent' ? 'an agent' : 'a human user';
|
|
184
|
-
const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
185
|
-
return sender ? `@${sender}, ${type}` : type;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function senderFactDescription(msg) {
|
|
189
|
-
const type = msg.sender_type === 'agent' ? 'agent' : 'human user';
|
|
190
|
-
const sender = msg.sender_display_name || msg.sender_user_id || msg.sender_agent_id || '';
|
|
191
|
-
return sender ? `@${sender}, ${type}` : type;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function conversationLabel(msg, target) {
|
|
195
|
-
const convType = msg.conversation_type || 'dm';
|
|
196
|
-
if (convType === 'dm') return 'a one-on-one conversation';
|
|
197
|
-
return target || 'this group';
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function buildMessageSummary(msg, target) {
|
|
201
|
-
const convType = msg.conversation_type || 'dm';
|
|
202
|
-
const reason = normalizeDeliveryReasonForPrompt(msg.reason || '');
|
|
203
|
-
const sender = senderDescription(msg);
|
|
204
|
-
const senderFact = senderFactDescription(msg);
|
|
205
|
-
const where = conversationLabel(msg, target);
|
|
206
|
-
|
|
207
|
-
if (convType === 'dm') {
|
|
208
|
-
return `This is a one-on-one message from ${sender}.`;
|
|
209
|
-
}
|
|
210
|
-
if (reason === 'assignment') {
|
|
211
|
-
return `This message assigns or routes work to you in ${where}.\nSender: ${senderFact}`;
|
|
212
|
-
}
|
|
213
|
-
if (reason === 'ambient') {
|
|
214
|
-
return `Sender: ${senderFact}`;
|
|
215
|
-
}
|
|
216
|
-
if (reason === 'mention') {
|
|
217
|
-
return `You were mentioned in ${where} by ${sender}.`;
|
|
218
|
-
}
|
|
219
|
-
if (reason === 'reply_follow') {
|
|
220
|
-
return `This is a reply in ${where} from ${sender}.`;
|
|
221
|
-
}
|
|
222
|
-
if (reason === 'manual') {
|
|
223
|
-
return `This is a manual wake-up or reminder for ${where}.\nSource: ${senderFact}`;
|
|
224
|
-
}
|
|
225
|
-
return `This is a message in ${where} from ${sender}.`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function buildTaskDetailsBlock(msg) {
|
|
229
|
-
if (msg.task_number == null) return '';
|
|
230
|
-
const lines = [`Task #${msg.task_number} is currently \`${msg.task_status || 'todo'}\`.`];
|
|
231
|
-
if (msg.task_title) lines.push(`Task title: ${msg.task_title}`);
|
|
232
|
-
if (msg.task_assignee_agent_id || msg.task_assignee_user_id) {
|
|
233
|
-
const t = msg.task_assignee_type || 'agent';
|
|
234
|
-
const id = msg.task_assignee_agent_id || msg.task_assignee_user_id;
|
|
235
|
-
lines.push(`Assignee: ${t} \`${id}\``);
|
|
236
|
-
}
|
|
237
|
-
return lines.join('\n');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function buildReactionsBlock(msg) {
|
|
241
|
-
const entries = Array.isArray(msg.reactions_summary) ? msg.reactions_summary : [];
|
|
242
|
-
if (entries.length === 0) return '';
|
|
243
|
-
return `Recent reactions: ${entries.join('; ')}`;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function buildContentBlock(rawText) {
|
|
247
|
-
const text = String(rawText || '').trim();
|
|
248
|
-
if (!text) return '';
|
|
249
|
-
return promptBlock(`
|
|
250
|
-
Content:
|
|
251
|
-
${text}
|
|
252
|
-
`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Wrap each per-turn message with the concrete dynamic context for this
|
|
256
|
-
// delivery. Durable communication rules are inlined into the standing
|
|
257
|
-
// prompt (see standing-prompt.mjs); this block only carries the current
|
|
258
|
-
// message and its exact reply target.
|
|
259
|
-
export function buildWakePromptText({ messageSummary, target, rawText, groupContext, charterBlock, goalStateBlock, quoteBlock, taskDetails, reactionsBlock }) {
|
|
260
|
-
const contextPrefix = [charterBlock, goalStateBlock, quoteBlock].filter(Boolean).join('\n\n');
|
|
261
|
-
const prefix = contextPrefix ? `${contextPrefix}\n\n` : '';
|
|
262
|
-
const detailBlocks = [
|
|
263
|
-
messageSummary,
|
|
264
|
-
`Reply target: \`${target}\``,
|
|
265
|
-
groupContext,
|
|
266
|
-
taskDetails ? `Task details:\n${taskDetails}` : '',
|
|
267
|
-
reactionsBlock,
|
|
268
|
-
buildContentBlock(rawText),
|
|
269
|
-
].filter(Boolean).join('\n\n');
|
|
270
|
-
return promptBlock(`
|
|
271
|
-
${prefix}New message received:
|
|
272
|
-
|
|
273
|
-
${detailBlocks}
|
|
274
|
-
`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export function buildInboundWakePrompt(msg) {
|
|
278
|
-
const rawText = msg.text || '';
|
|
279
|
-
const baseHeader = buildDebugEnvelopeHeader(msg);
|
|
280
|
-
const header = baseHeader + buildDebugTaskSuffix(msg) + buildDebugReactionsSuffix(msg);
|
|
281
|
-
const target = buildEnvelopeTarget(msg);
|
|
282
|
-
const groupContext = buildGroupContextBlock(msg);
|
|
283
|
-
const charterBlock = buildCharterBlock(msg);
|
|
284
|
-
const goalStateBlock = buildGoalStateBlock(msg);
|
|
285
|
-
const quoteBlock = buildQuoteBlock(msg, target);
|
|
286
|
-
const messageSummary = buildMessageSummary(msg, target);
|
|
287
|
-
const taskDetails = buildTaskDetailsBlock(msg);
|
|
288
|
-
const reactionsBlock = buildReactionsBlock(msg);
|
|
289
|
-
const text = buildWakePromptText({ messageSummary, target, rawText, groupContext, charterBlock, goalStateBlock, quoteBlock, taskDetails, reactionsBlock });
|
|
290
|
-
return {
|
|
291
|
-
header,
|
|
292
|
-
target,
|
|
293
|
-
text,
|
|
294
|
-
rawText,
|
|
295
|
-
};
|
|
296
|
-
}
|