ticlawk 0.1.17-dev.2 → 0.1.17-dev.20
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 +26 -59
- package/bin/ticlawk.mjs +31 -301
- package/package.json +4 -2
- package/scripts/publish-dev.sh +77 -0
- package/src/adapters/ticlawk/api.mjs +50 -378
- package/src/adapters/ticlawk/credentials.mjs +1 -43
- package/src/adapters/ticlawk/index.mjs +61 -565
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +18 -715
- package/src/core/adapter-registry.mjs +1 -19
- package/src/core/agent-cli-handlers.mjs +18 -556
- package/src/core/agent-home.mjs +1 -81
- package/src/core/events/worker-events.mjs +36 -32
- package/src/core/http.mjs +0 -152
- package/src/core/profiles.mjs +0 -1
- package/src/core/runtime-contract.mjs +1 -0
- package/src/core/runtime-env.mjs +0 -8
- 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 +32 -5
- package/src/runtimes/_shared/agent-handbook.mjs +0 -45
- package/src/runtimes/_shared/brand.mjs +0 -2
- package/src/runtimes/_shared/goal-step-prompt.mjs +0 -98
- package/src/runtimes/_shared/goal-task-protocol.mjs +0 -50
- package/src/runtimes/_shared/handbook/BASICS.md +0 -27
- package/src/runtimes/_shared/handbook/COLLABORATION.md +0 -37
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +0 -55
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +0 -13
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +0 -47
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +0 -43
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +0 -21
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +0 -15
- package/src/runtimes/_shared/handbook/SURFACES.md +0 -41
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +0 -14
- package/src/runtimes/_shared/standing-prompt.mjs +0 -171
- package/src/runtimes/_shared/wake-prompt.mjs +0 -268
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { basename } from 'node:path';
|
|
8
8
|
import { buildAgentRuntimeEnv } from '../../core/runtime-env.mjs';
|
|
9
|
-
import {
|
|
10
|
-
import { buildStandingPrompt } from '../_shared/standing-prompt.mjs';
|
|
9
|
+
import { buildRuntimeBaseInstructions } from '../_shared/runtime-base-instructions.mjs';
|
|
11
10
|
import { ensureAgentHome } from '../../core/agent-home.mjs';
|
|
12
11
|
import {
|
|
13
12
|
buildPiImagesFromInbound,
|
|
@@ -19,18 +18,15 @@ import {
|
|
|
19
18
|
requirePiPath,
|
|
20
19
|
runPiPrompt,
|
|
21
20
|
} from './session.mjs';
|
|
22
|
-
import {
|
|
21
|
+
import { emitWorkerEvent } from '../../core/events/worker-events.mjs';
|
|
23
22
|
import {
|
|
24
23
|
shouldStreamRuntime,
|
|
24
|
+
createDeltaAggregator,
|
|
25
25
|
reportSubprocessFailure,
|
|
26
26
|
terminalRuntimeFailure,
|
|
27
27
|
updateBindingRuntimeMeta,
|
|
28
|
-
resolveRuntimeSessionScope,
|
|
29
|
-
buildRuntimeSessionMetaPatch,
|
|
30
28
|
} from '../../core/runtime-support.mjs';
|
|
31
29
|
|
|
32
|
-
const standingPromptSeen = new Set();
|
|
33
|
-
|
|
34
30
|
export const piRuntime = {
|
|
35
31
|
name: 'pi',
|
|
36
32
|
|
|
@@ -42,8 +38,7 @@ export const piRuntime = {
|
|
|
42
38
|
images: opts.images,
|
|
43
39
|
piPath,
|
|
44
40
|
agentEnv,
|
|
45
|
-
|
|
46
|
-
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
41
|
+
runtimeBaseInstructions: opts.runtimeBaseInstructions || null,
|
|
47
42
|
timeoutMs: opts.timeoutMs,
|
|
48
43
|
});
|
|
49
44
|
},
|
|
@@ -56,8 +51,7 @@ export const piRuntime = {
|
|
|
56
51
|
images: opts.images,
|
|
57
52
|
piPath,
|
|
58
53
|
agentEnv,
|
|
59
|
-
|
|
60
|
-
forceStandingPrompt: Boolean(opts.forceStandingPrompt),
|
|
54
|
+
runtimeBaseInstructions: opts.runtimeBaseInstructions || null,
|
|
61
55
|
timeoutMs: opts.timeoutMs,
|
|
62
56
|
onEvent: opts.onEvent,
|
|
63
57
|
});
|
|
@@ -134,39 +128,46 @@ export const piRuntime = {
|
|
|
134
128
|
message = captionText || 'Please analyze the attached image(s).';
|
|
135
129
|
}
|
|
136
130
|
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
131
|
+
const shouldRotate = !meta.sessionId || meta.rotatePending;
|
|
132
|
+
const deltaAggregator = createDeltaAggregator({
|
|
133
|
+
flushDelta: async ({ text, sessionId, cwd }) => {
|
|
134
|
+
await emitWorkerEvent({
|
|
135
|
+
adapter,
|
|
136
|
+
binding,
|
|
137
|
+
agent: this.name,
|
|
138
|
+
sessionId: sessionId || meta.sessionId || binding.id,
|
|
139
|
+
cwd: cwd || agentHome,
|
|
140
|
+
replyToMessageId: inbound.messageId || null,
|
|
141
|
+
event: {
|
|
142
|
+
hook_event_name: 'worker.message.delta',
|
|
143
|
+
worker_event_name: 'worker.message.delta',
|
|
144
|
+
delta: text,
|
|
145
|
+
},
|
|
146
|
+
logger: ctx.logger,
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
});
|
|
140
150
|
|
|
141
151
|
try {
|
|
142
152
|
const runtimePiPath = requirePiPath(meta.piPath || meta.runtimePath);
|
|
143
153
|
const runtimePiVersion = getPiRuntimeHealth(runtimePiPath).version || meta.piVersion || null;
|
|
144
154
|
const agentEnv = buildAgentRuntimeEnv({
|
|
145
155
|
agentId: binding.id,
|
|
146
|
-
sessionId:
|
|
156
|
+
sessionId: meta.sessionId,
|
|
147
157
|
hostId: binding.runtime_host_id,
|
|
148
|
-
conversationId: inbound.conversationId,
|
|
149
|
-
messageId: inbound.messageId,
|
|
150
|
-
target: inbound.envelopeTarget,
|
|
151
158
|
});
|
|
152
|
-
const
|
|
153
|
-
const protocolOverlayKey = getGoalTaskProtocolOverlayKey({ agentId: binding.id, inbound });
|
|
154
|
-
const standingPromptSeenKey = targetSessionId
|
|
155
|
-
? `${binding.id}|${targetSessionId}|${protocolOverlayKey}`
|
|
156
|
-
: null;
|
|
157
|
-
const forceStandingPrompt = Boolean(standingPromptSeenKey && !standingPromptSeen.has(standingPromptSeenKey));
|
|
159
|
+
const runtimeBaseInstructions = buildRuntimeBaseInstructions({ agentId: binding.id });
|
|
158
160
|
const result = shouldStreamRuntime(this.name, this)
|
|
159
|
-
? await this.runTurnStream({ sessionId:
|
|
160
|
-
|
|
161
|
-
forceStandingPrompt,
|
|
161
|
+
? await this.runTurnStream({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, {
|
|
162
|
+
runtimeBaseInstructions,
|
|
162
163
|
images,
|
|
163
164
|
onEvent: async (event) => {
|
|
164
165
|
if (event?.type === 'turn.started') {
|
|
165
|
-
|
|
166
|
+
await emitWorkerEvent({
|
|
166
167
|
adapter,
|
|
167
168
|
binding,
|
|
168
169
|
agent: this.name,
|
|
169
|
-
sessionId: event.sessionId ||
|
|
170
|
+
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
170
171
|
cwd: agentHome,
|
|
171
172
|
replyToMessageId: inbound.messageId || null,
|
|
172
173
|
event: {
|
|
@@ -175,29 +176,31 @@ export const piRuntime = {
|
|
|
175
176
|
},
|
|
176
177
|
logger: ctx.logger,
|
|
177
178
|
});
|
|
179
|
+
} else if (event?.type === 'message.delta' && event.text) {
|
|
180
|
+
deltaAggregator.push(event.text, {
|
|
181
|
+
sessionId: event.sessionId || meta.sessionId || binding.id,
|
|
182
|
+
cwd: agentHome,
|
|
183
|
+
});
|
|
178
184
|
}
|
|
179
185
|
},
|
|
180
186
|
})
|
|
181
|
-
: await this.runTurn({ sessionId:
|
|
187
|
+
: await this.runTurn({ sessionId: shouldRotate ? null : meta.sessionId, cwd: agentHome, piPath: runtimePiPath, agentEnv }, message, { images, runtimeBaseInstructions });
|
|
182
188
|
|
|
183
|
-
|
|
184
|
-
if (observedSessionId) {
|
|
185
|
-
standingPromptSeen.add(`${binding.id}|${observedSessionId}|${protocolOverlayKey}`);
|
|
186
|
-
}
|
|
189
|
+
await deltaAggregator.flush();
|
|
187
190
|
const nextBinding = await updateBindingRuntimeMeta(ctx, binding, {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
path: result?.path,
|
|
191
|
-
}),
|
|
191
|
+
sessionId: result?.sessionId || meta.sessionId,
|
|
192
|
+
path: result?.path || meta.path || null,
|
|
192
193
|
runtimePath: runtimePiPath,
|
|
193
194
|
piPath: runtimePiPath,
|
|
194
195
|
piVersion: runtimePiVersion,
|
|
196
|
+
rotatePending: false,
|
|
197
|
+
lastRotatedAt: shouldRotate ? new Date().toISOString() : meta.lastRotatedAt,
|
|
195
198
|
}, { status: 'connected' });
|
|
196
|
-
|
|
199
|
+
await emitWorkerEvent({
|
|
197
200
|
adapter,
|
|
198
201
|
binding: nextBinding,
|
|
199
202
|
agent: this.name,
|
|
200
|
-
sessionId: result?.sessionId ||
|
|
203
|
+
sessionId: result?.sessionId || meta.sessionId || binding.id,
|
|
201
204
|
cwd: result?.cwd || agentHome,
|
|
202
205
|
replyToMessageId: inbound.messageId || null,
|
|
203
206
|
event: {
|
|
@@ -208,11 +211,12 @@ export const piRuntime = {
|
|
|
208
211
|
});
|
|
209
212
|
return true;
|
|
210
213
|
} catch (err) {
|
|
211
|
-
|
|
214
|
+
await deltaAggregator.flush().catch(() => {});
|
|
215
|
+
await emitWorkerEvent({
|
|
212
216
|
adapter,
|
|
213
217
|
binding,
|
|
214
218
|
agent: this.name,
|
|
215
|
-
sessionId:
|
|
219
|
+
sessionId: meta.sessionId || binding.id,
|
|
216
220
|
cwd: agentHome,
|
|
217
221
|
replyToMessageId: inbound.messageId || null,
|
|
218
222
|
event: {
|
|
@@ -238,6 +242,24 @@ export const piRuntime = {
|
|
|
238
242
|
}
|
|
239
243
|
},
|
|
240
244
|
|
|
245
|
+
async reconcileAfterRestart(binding, ctx) {
|
|
246
|
+
const meta = binding.runtimeMeta || {};
|
|
247
|
+
await emitWorkerEvent({
|
|
248
|
+
adapter: ctx.adapter,
|
|
249
|
+
binding,
|
|
250
|
+
agent: this.name,
|
|
251
|
+
sessionId: meta.sessionId || binding.id,
|
|
252
|
+
cwd: ensureAgentHome(binding.id) || '',
|
|
253
|
+
event: {
|
|
254
|
+
hook_event_name: 'Stop',
|
|
255
|
+
worker_event_name: 'worker.turn.complete',
|
|
256
|
+
reason: 'connector.restart.reconcile',
|
|
257
|
+
},
|
|
258
|
+
logger: ctx.logger,
|
|
259
|
+
});
|
|
260
|
+
return 1;
|
|
261
|
+
},
|
|
262
|
+
|
|
241
263
|
sessionsDir: PI_SESSIONS_DIR,
|
|
242
264
|
maxAgeMs: PI_MAX_AGE_MS,
|
|
243
265
|
};
|
|
@@ -196,16 +196,13 @@ export function runPiPrompt({
|
|
|
196
196
|
images = [],
|
|
197
197
|
piPath = null,
|
|
198
198
|
agentEnv = null,
|
|
199
|
-
|
|
200
|
-
forceStandingPrompt = false,
|
|
199
|
+
runtimeBaseInstructions = null,
|
|
201
200
|
timeoutMs = Number(process.env.PI_RUN_TIMEOUT_MS || DEFAULT_PI_RUN_TIMEOUT_MS),
|
|
202
201
|
onEvent,
|
|
203
202
|
}) {
|
|
204
|
-
// pi has no documented system-prompt flag
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const finalMessage = standingPrompt && (!sessionId || forceStandingPrompt)
|
|
208
|
-
? `${standingPrompt}\n\n---\n\n${message}`
|
|
203
|
+
// pi has no documented system-prompt flag — prepend on first turn.
|
|
204
|
+
const finalMessage = runtimeBaseInstructions && !sessionId
|
|
205
|
+
? `${runtimeBaseInstructions}\n\n---\n\n${message}`
|
|
209
206
|
: message;
|
|
210
207
|
return new Promise((resolve, reject) => {
|
|
211
208
|
const startedAt = Date.now();
|
|
@@ -223,13 +220,12 @@ export function runPiPrompt({
|
|
|
223
220
|
let activeSessionFile = null;
|
|
224
221
|
let finalText = '';
|
|
225
222
|
let settled = false;
|
|
223
|
+
let eventChain = Promise.resolve();
|
|
226
224
|
const pending = new Map();
|
|
227
225
|
|
|
228
226
|
const emit = (event) => {
|
|
229
227
|
if (typeof onEvent !== 'function') return;
|
|
230
|
-
|
|
231
|
-
.then(() => onEvent(event))
|
|
232
|
-
.catch(() => {});
|
|
228
|
+
eventChain = eventChain.then(() => onEvent(event)).catch(() => {});
|
|
233
229
|
};
|
|
234
230
|
|
|
235
231
|
const settle = (fn, value) => {
|
|
@@ -241,7 +237,7 @@ export function runPiPrompt({
|
|
|
241
237
|
}
|
|
242
238
|
pending.clear();
|
|
243
239
|
try { child.kill('SIGTERM'); } catch {}
|
|
244
|
-
fn(value);
|
|
240
|
+
eventChain.catch(() => {}).finally(() => fn(value));
|
|
245
241
|
};
|
|
246
242
|
|
|
247
243
|
const sendRaw = (payload) => {
|
|
@@ -309,6 +305,7 @@ export function runPiPrompt({
|
|
|
309
305
|
const delta = extractDeltaFromEvent(event);
|
|
310
306
|
if (delta) {
|
|
311
307
|
finalText += delta;
|
|
308
|
+
emit({ type: 'message.delta', sessionId: activeSessionId, text: delta });
|
|
312
309
|
}
|
|
313
310
|
return;
|
|
314
311
|
}
|
package/ticlawk.mjs
CHANGED
|
@@ -211,6 +211,35 @@ async function recoverAllRuntimes(runtimeList, adapter) {
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
async function reconcileBindingsAfterRestart(runtimes, adapter) {
|
|
215
|
+
const hostId = getHostId();
|
|
216
|
+
for (const binding of listBindings({ adapter: adapter.id })) {
|
|
217
|
+
if (!belongsToRuntimeHost(binding, hostId)) {
|
|
218
|
+
logger.debugError('core', 'binding.reconcile-host-mismatch', {
|
|
219
|
+
bindingId: binding.id,
|
|
220
|
+
adapter: binding.adapter,
|
|
221
|
+
hostId,
|
|
222
|
+
runtime_host_id: getBindingRuntimeHostId(binding),
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const runtime = binding.runtime ? runtimes[binding.runtime] : null;
|
|
227
|
+
if (typeof runtime?.reconcileAfterRestart !== 'function') continue;
|
|
228
|
+
try {
|
|
229
|
+
await runtime.reconcileAfterRestart(binding, {
|
|
230
|
+
adapter,
|
|
231
|
+
logger,
|
|
232
|
+
});
|
|
233
|
+
} catch (err) {
|
|
234
|
+
logger.debugError('startup', 'reconcileAfterRestart.failed', {
|
|
235
|
+
runtime: binding.runtime,
|
|
236
|
+
bindingId: binding.id,
|
|
237
|
+
error: err?.message || String(err),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
214
243
|
export async function startTiclawk() {
|
|
215
244
|
if (started) return;
|
|
216
245
|
started = true;
|
|
@@ -237,19 +266,17 @@ export async function startTiclawk() {
|
|
|
237
266
|
baseRuntimeCtx = createBaseRuntimeCtx(runtimes, persistBinding, (binding) => syncBinding(binding));
|
|
238
267
|
|
|
239
268
|
printBanner(adapter);
|
|
240
|
-
if (typeof adapter.refreshBindings === 'function') {
|
|
241
|
-
await adapter.refreshBindings();
|
|
242
|
-
}
|
|
243
269
|
registerRuntimeHandlers(runtimeList, baseRuntimeCtx, adapter);
|
|
244
|
-
await replayBindings(runtimes, adapter);
|
|
245
270
|
startLocalHttpServer({
|
|
246
271
|
port: HTTP_PORT,
|
|
247
272
|
adapter,
|
|
248
273
|
ctx: { listBindings, getBinding },
|
|
249
274
|
});
|
|
250
275
|
startReminderTicker();
|
|
251
|
-
await recoverAllRuntimes(runtimeList, adapter);
|
|
252
276
|
await adapter.start();
|
|
277
|
+
await replayBindings(runtimes, adapter);
|
|
278
|
+
await recoverAllRuntimes(runtimeList, adapter);
|
|
279
|
+
await reconcileBindingsAfterRestart(runtimes, adapter);
|
|
253
280
|
}
|
|
254
281
|
|
|
255
282
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
5
|
-
export const HANDBOOK_BASICS_FILE = 'BASICS.md';
|
|
6
|
-
|
|
7
|
-
export const HANDBOOK_FILE_NAMES = Object.freeze([
|
|
8
|
-
HANDBOOK_BASICS_FILE,
|
|
9
|
-
'COMMUNICATION.md',
|
|
10
|
-
'COLLABORATION.md',
|
|
11
|
-
'GOAL_TASK_CORE.md',
|
|
12
|
-
'GOAL_AUTHORITY.md',
|
|
13
|
-
'TASK_WORKER.md',
|
|
14
|
-
'DM_SCOPE.md',
|
|
15
|
-
'GROUP_ADMIN_SCOPE.md',
|
|
16
|
-
'GROUP_MEMBER_SCOPE.md',
|
|
17
|
-
'SURFACES.md',
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
export const LEGACY_HANDBOOK_FILE_NAMES = Object.freeze([
|
|
21
|
-
'TICLAWK.md',
|
|
22
|
-
'TICLAWK_CLI.md',
|
|
23
|
-
'TICLAWK_COLLABORATION.md',
|
|
24
|
-
'TICLAWK_GOAL_TASK_PROTOCOL.md',
|
|
25
|
-
'TICLAWK_GOAL_TASK_CORE.md',
|
|
26
|
-
'TICLAWK_GOAL_AUTHORITY.md',
|
|
27
|
-
'TICLAWK_TASK_WORKER.md',
|
|
28
|
-
'TICLAWK_DM_SCOPE.md',
|
|
29
|
-
'TICLAWK_GROUP_ADMIN_SCOPE.md',
|
|
30
|
-
'TICLAWK_GROUP_MEMBER_SCOPE.md',
|
|
31
|
-
'TICLAWK_SURFACES.md',
|
|
32
|
-
'TICLAWK_WORKSPACE.md',
|
|
33
|
-
'WORKSPACE.md',
|
|
34
|
-
'GOAL_TASK_PROTOCOL.md',
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
38
|
-
const HANDBOOK_DIR = join(MODULE_DIR, 'handbook');
|
|
39
|
-
|
|
40
|
-
export function buildAgentHandbookFiles() {
|
|
41
|
-
return HANDBOOK_FILE_NAMES.map((name) => ({
|
|
42
|
-
name,
|
|
43
|
-
content: readFileSync(join(HANDBOOK_DIR, name), 'utf8').trim(),
|
|
44
|
-
}));
|
|
45
|
-
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-step goal-lane (FSM) prompt builder.
|
|
3
|
-
*
|
|
4
|
-
* A transition delivery is an FSM event, not a chat message: it has no
|
|
5
|
-
* backing message, only a payload carrying { transition_id, kind,
|
|
6
|
-
* goal_version, step }. The goal lane runs exactly the step named in the
|
|
7
|
-
* payload, then reports the outcome with `ticlawk goal report`, which
|
|
8
|
-
* advances the state machine and (for running states) schedules the next
|
|
9
|
-
* step as a fresh transition. This is the goal-lane analogue of
|
|
10
|
-
* buildInboundWakePrompt — same {header, target, text, rawText} contract.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { buildEnvelopeTarget, buildCharterBlock } from './wake-prompt.mjs';
|
|
14
|
-
|
|
15
|
-
const STEP_GUIDES = {
|
|
16
|
-
gap_analysis: {
|
|
17
|
-
title: 'GAP ANALYSIS',
|
|
18
|
-
body: `Compare the current state of the work against the goal and success criteria. Read whatever you need (charter, dashboard, task board, repo, prior messages) to judge where things actually stand.
|
|
19
|
-
- If there is concrete executable work toward the goal, first make sure the next unit exists as a task (create/assign it with \`ticlawk task ...\` when a task fits), then report outcome=gap.
|
|
20
|
-
- If the goal/milestone is fully met with no meaningful gap, report outcome=no_gap.
|
|
21
|
-
- If closing the gap depends on a future or external state that nobody can act on right now, schedule a reminder for the resume condition, then report outcome=wait.`,
|
|
22
|
-
outcomes: ['gap', 'no_gap', 'wait'],
|
|
23
|
-
},
|
|
24
|
-
execute: {
|
|
25
|
-
title: 'EXECUTE',
|
|
26
|
-
body: `Do the next concrete unit of work toward the goal (or drive the current task to completion). Send interim updates with \`ticlawk message send --phase progress\` as you go.
|
|
27
|
-
- When the unit of work is finished and ready to be checked, report outcome=task_completed.
|
|
28
|
-
- If you cannot proceed without an owner approval, decision, or permission, park ONE canonical approval with \`ticlawk approval request --title "<what you need approved>" [--detail "<context>"]\`, tell the owner what you need and why with \`ticlawk message send\`, then report outcome=needs_approval. The owner's approval (button or a natural-language reply) resumes you automatically.
|
|
29
|
-
- If you are blocked by something else (missing input, external failure, a needed resource), explain it to the owner, then report outcome=blocked.`,
|
|
30
|
-
outcomes: ['task_completed', 'needs_approval', 'blocked'],
|
|
31
|
-
},
|
|
32
|
-
review: {
|
|
33
|
-
title: 'REVIEW',
|
|
34
|
-
body: `Review the work that was just completed against the task and the goal.
|
|
35
|
-
- If it meets the bar, mark the task done (\`ticlawk task update ... --status done\` where applicable) and report outcome=accepted.
|
|
36
|
-
- If it needs rework, return it with a clean, specific redo instruction, then report outcome=rejected.`,
|
|
37
|
-
outcomes: ['accepted', 'rejected'],
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
function readPayload(msg) {
|
|
42
|
-
const payload = msg?.payload && typeof msg.payload === 'object' ? msg.payload : {};
|
|
43
|
-
return {
|
|
44
|
-
transitionId: String(payload.transition_id || '').trim(),
|
|
45
|
-
goalVersion: payload.goal_version != null ? String(payload.goal_version) : '',
|
|
46
|
-
kind: String(payload.kind || '').trim(),
|
|
47
|
-
step: String(payload.step || '').trim(),
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function buildGoalStepHeader(msg, { step, transitionId, goalVersion, kind }) {
|
|
52
|
-
const target = buildEnvelopeTarget(msg);
|
|
53
|
-
const time = msg.created_at || new Date().toISOString();
|
|
54
|
-
return `[goal_lane target=${target} step=${step || 'unknown'} transition=${transitionId} goal_version=${goalVersion} kind=${kind} time=${time}]`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function buildGoalStepPrompt(msg) {
|
|
58
|
-
const { transitionId, goalVersion, kind, step } = readPayload(msg);
|
|
59
|
-
const target = buildEnvelopeTarget(msg);
|
|
60
|
-
const conversationId = msg.conversation_id || '';
|
|
61
|
-
const header = buildGoalStepHeader(msg, { step, transitionId, goalVersion, kind });
|
|
62
|
-
const charterBlock = buildCharterBlock(msg);
|
|
63
|
-
const guide = STEP_GUIDES[step] || null;
|
|
64
|
-
|
|
65
|
-
const reportCmd = `ticlawk goal report --conversation ${conversationId} --transition ${transitionId} --outcome <${guide ? guide.outcomes.join('|') : 'outcome'}>`;
|
|
66
|
-
|
|
67
|
-
const sections = [];
|
|
68
|
-
if (charterBlock) sections.push(charterBlock);
|
|
69
|
-
|
|
70
|
-
if (guide) {
|
|
71
|
-
sections.push([
|
|
72
|
-
`[goal_step] You are running the goal loop for this conversation. This is a backend FSM step, not a user message — do NOT treat it as something to reply to.`,
|
|
73
|
-
``,
|
|
74
|
-
`Current step: ${guide.title}`,
|
|
75
|
-
guide.body,
|
|
76
|
-
``,
|
|
77
|
-
`When the step is done, advance the state machine by running EXACTLY ONE report:`,
|
|
78
|
-
` ${reportCmd}`,
|
|
79
|
-
``,
|
|
80
|
-
`Reporting the outcome is what continues the loop: a running next state arrives as a fresh step, and the loop parks itself when there is no gap or it must wait. Send owner-facing updates with \`ticlawk message send --target ${target} --phase progress\` (use --phase final only when the loop reaches no_gap/wait/blocked-on-owner). Report exactly once; do not loop inside this single turn.`,
|
|
81
|
-
`[/goal_step]`,
|
|
82
|
-
].join('\n'));
|
|
83
|
-
} else {
|
|
84
|
-
sections.push([
|
|
85
|
-
`[goal_step] Goal-loop FSM step with an unrecognized step "${step}". Re-evaluate the goal as a gap analysis: decide whether there is a gap, and report with:`,
|
|
86
|
-
` ${reportCmd}`,
|
|
87
|
-
`[/goal_step]`,
|
|
88
|
-
].join('\n'));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const text = sections.filter(Boolean).join('\n\n');
|
|
92
|
-
return {
|
|
93
|
-
header,
|
|
94
|
-
target,
|
|
95
|
-
text,
|
|
96
|
-
rawText: '',
|
|
97
|
-
};
|
|
98
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime goal/task branch selection.
|
|
3
|
-
*
|
|
4
|
-
* Prompt text lives in the managed handbook files. This module only derives
|
|
5
|
-
* the current conversation scope, role, and goal-authority branch for runtime
|
|
6
|
-
* prompt selection and cache keys.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
function getInboundRaw(ctx = {}) {
|
|
10
|
-
return ctx?.inbound?.raw && typeof ctx.inbound.raw === 'object'
|
|
11
|
-
? ctx.inbound.raw
|
|
12
|
-
: {};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function inferScope(ctx = {}) {
|
|
16
|
-
const raw = getInboundRaw(ctx);
|
|
17
|
-
const conversationType = String(raw.conversation_type || '').trim();
|
|
18
|
-
if (conversationType === 'group' || conversationType === 'thread') return 'group';
|
|
19
|
-
return 'dm';
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getRecipientConversationRole(ctx = {}) {
|
|
23
|
-
const raw = getInboundRaw(ctx);
|
|
24
|
-
return String(raw.recipient_conversation_role || raw.recipient_role || '').trim().toLowerCase() || 'member';
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function hasConversationAdminRole(ctx = {}) {
|
|
28
|
-
const raw = getInboundRaw(ctx);
|
|
29
|
-
if (raw.recipient_is_conversation_admin === true) return true;
|
|
30
|
-
const role = getRecipientConversationRole(ctx);
|
|
31
|
-
return role === 'admin' || role === 'owner';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function hasGoalAuthority(ctx = {}) {
|
|
35
|
-
const scope = inferScope(ctx);
|
|
36
|
-
if (scope === 'dm') return true;
|
|
37
|
-
return hasConversationAdminRole(ctx);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function selectGoalTaskProtocolOverlays(ctx = {}) {
|
|
41
|
-
const scope = inferScope(ctx);
|
|
42
|
-
const recipientRole = getRecipientConversationRole(ctx);
|
|
43
|
-
const goalAuthority = hasGoalAuthority(ctx);
|
|
44
|
-
return { scope, recipientRole, goalAuthority };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function getGoalTaskProtocolOverlayKey(ctx = {}) {
|
|
48
|
-
const overlays = selectGoalTaskProtocolOverlays(ctx);
|
|
49
|
-
return `${overlays.scope}:${overlays.recipientRole}:${overlays.goalAuthority ? 'goal' : 'task'}`;
|
|
50
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Agent Basics
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
## Workspace
|
|
6
|
-
|
|
7
|
-
- Your cwd is a persistent, agent-owned workspace.
|
|
8
|
-
- Use it for memory, notes, artifacts, code checkouts, and task files.
|
|
9
|
-
- `MEMORY.md` is the recovery entry point. Read it before acting.
|
|
10
|
-
- Use `notes/<topic>.md` for long-lived detail and link those notes from `MEMORY.md`.
|
|
11
|
-
- Do not store secrets, chat transcripts, or routine progress logs in memory.
|
|
12
|
-
- When working in a repository, choose the specific project directory or worktree before running git or package commands.
|
|
13
|
-
|
|
14
|
-
## Work Contract
|
|
15
|
-
|
|
16
|
-
- Normal assistant output is private activity text inside your workspace. It is not sent to users, groups, or other agents.
|
|
17
|
-
- External communication goes through the `ticlawk` CLI.
|
|
18
|
-
- Complete the requested work before stopping. A progress update is allowed, but it is not completion.
|
|
19
|
-
- Read only the local files needed for the current turn.
|
|
20
|
-
|
|
21
|
-
## Memory Updates
|
|
22
|
-
|
|
23
|
-
Update `MEMORY.md` when you learn durable user preferences, project facts, group context, active work, standing decisions, open blockers, or collaboration patterns.
|
|
24
|
-
|
|
25
|
-
Keep memory concise. Link detailed notes instead of turning `MEMORY.md` into a transcript.
|
|
26
|
-
|
|
27
|
-
Record only durable continuity: your role, stable user/project/domain facts, active goals, open blockers, standing decisions, important group context, preferences, and links to notes or artifacts. Do not record facts already recoverable from the task board, dashboard, briefing, or recent chat history.
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# Collaboration
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
## DMs
|
|
6
|
-
|
|
7
|
-
- In a DM, handle the direct ask and own follow-through until it is answered, blocked, transferred, or complete.
|
|
8
|
-
- If the DM refers to group or task work, route it back to the relevant group or task while still answering the user clearly.
|
|
9
|
-
|
|
10
|
-
## Groups
|
|
11
|
-
|
|
12
|
-
- Direct mentions, DMs, assignments, and manual reminders normally require action.
|
|
13
|
-
- Ambient group messages are visible context, not automatic work. Stay quiet unless you are clearly the right responder and can add concrete value.
|
|
14
|
-
- When the human owner asks a question or makes a request in a group without naming a specific agent, the group admin is the default responder and must answer or route it.
|
|
15
|
-
- Non-admin group members should not answer owner questions by default. Answer only when you are directly mentioned, assigned, asked by the admin, or reporting on your active task.
|
|
16
|
-
- Write like a teammate coordinating work, not like a protocol trace.
|
|
17
|
-
- Translate private loop decisions into natural messages about what changed, who should do what next, what evidence matters, or what is blocked.
|
|
18
|
-
- Do not echo someone else's completion report or PR summary. The person doing the work should report on it.
|
|
19
|
-
|
|
20
|
-
## Assigning Work
|
|
21
|
-
|
|
22
|
-
When assigning work, send a compact instruction with:
|
|
23
|
-
|
|
24
|
-
- desired outcome
|
|
25
|
-
- important constraints
|
|
26
|
-
- expected evidence
|
|
27
|
-
- where to report back
|
|
28
|
-
|
|
29
|
-
Avoid exposing internal checklists unless the group explicitly asks for process detail.
|
|
30
|
-
|
|
31
|
-
## Blockers
|
|
32
|
-
|
|
33
|
-
If you own a concrete blocker before stopping, follow the goal/task protocol for owner intervention when applicable. Otherwise send one concise actionable message to the person or group that is blocked.
|
|
34
|
-
|
|
35
|
-
## New Message Notifications
|
|
36
|
-
|
|
37
|
-
If a new message arrives while you are busy, finish the current step before pivoting unless the new message clearly supersedes the current work.
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# Communication
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Use `ticlawk` for all external communication. Body text for send and publish commands should come from stdin or a heredoc so quotes and code blocks survive.
|
|
6
|
-
|
|
7
|
-
## Incoming Messages
|
|
8
|
-
|
|
9
|
-
Each incoming message tells you:
|
|
10
|
-
|
|
11
|
-
- who sent the message
|
|
12
|
-
- whether it is a DM, mention, assignment, background group context, reply follow-up, or manual wake-up
|
|
13
|
-
- the exact reply target to use if you reply
|
|
14
|
-
- any goal, task, group, quote, or reaction context attached to this turn
|
|
15
|
-
|
|
16
|
-
The reply target is the precise chat destination for this message. Treat it as an exact command argument, not a description to rewrite.
|
|
17
|
-
|
|
18
|
-
## Replies
|
|
19
|
-
|
|
20
|
-
- To reply, copy the reply target from the current incoming message exactly.
|
|
21
|
-
- Do not infer or rewrite the destination.
|
|
22
|
-
- Send chat messages with `ticlawk message send --target <target> --phase progress|final`.
|
|
23
|
-
Message body rules:
|
|
24
|
-
- Write in natural language with normal paragraph breaks; do not use Markdown formatting.
|
|
25
|
-
- Use `--phase progress` for any intermediate reply, status update, partial result, or handoff before the current work is done.
|
|
26
|
-
- Use `--phase final` only for the final visible message of the current wake, after the user's request is handled and no more work remains before stopping.
|
|
27
|
-
- When the work is complete or blocked, send a concise final message with `--phase final`.
|
|
28
|
-
- If you send more than one message in the same turn, do not repeat content already sent.
|
|
29
|
-
- Use `--attach <path>` on `message send` when the user or group should receive a file.
|
|
30
|
-
- When reporting a result to a human, first decide whether the result itself is a file or artifact. If it is, attach it. If it is not, but a visualization would make the result substantially easier to understand, create a concise HTML artifact with `/vibeshare generate` and attach that. Do not create artifacts for ordinary text answers.
|
|
31
|
-
- Keep external messages clean and actionable: answer, instruction, blocker, decision request, or final result.
|
|
32
|
-
- Do not send private scratchpad unless the owner explicitly asks for that analysis.
|
|
33
|
-
- After that final send succeeds, output exactly `<turn_end>` in normal assistant output and stop. Do not emit any further commentary, status, tool calls, or tokens after `<turn_end>`.
|
|
34
|
-
|
|
35
|
-
## Reading Context
|
|
36
|
-
|
|
37
|
-
- `ticlawk message read --target <target>` reads recent conversation context.
|
|
38
|
-
- `ticlawk message read --target <target> --around <message-id>` inspects context around a specific message.
|
|
39
|
-
- `ticlawk message react` is a lightweight acknowledgement; use it sparingly.
|
|
40
|
-
|
|
41
|
-
## Groups And People
|
|
42
|
-
|
|
43
|
-
- `ticlawk server info` inspects visible groups, agents, and humans.
|
|
44
|
-
- `ticlawk group members --target <target>` inspects participants and roles in a group.
|
|
45
|
-
- `ticlawk group list` lists visible groups.
|
|
46
|
-
- `ticlawk agent list` lists visible owned agents.
|
|
47
|
-
|
|
48
|
-
## Follow-Up And Shared Tools
|
|
49
|
-
|
|
50
|
-
- The daemon wakes you for new messages and reminders; you do not need to poll.
|
|
51
|
-
- When future self-resume is needed, schedule a reminder.
|
|
52
|
-
- `ticlawk reminder schedule/snooze/update/cancel` is for visible, persistent future follow-up.
|
|
53
|
-
- `ticlawk service list/info/call` uses shared services when a published tool matches the task.
|
|
54
|
-
- `ticlawk credential request` asks the owner to provide credentials when work is blocked on secrets or account access.
|
|
55
|
-
- `ticlawk attachment view` inspects private chat assets when needed.
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# DM Scope
|
|
2
|
-
|
|
3
|
-
DO NOT EDIT.
|
|
4
|
-
|
|
5
|
-
Use this when the current conversation is a DM.
|
|
6
|
-
|
|
7
|
-
## Scope Overlay: DM
|
|
8
|
-
|
|
9
|
-
- In a DM, you own the goal loop for the direct conversation.
|
|
10
|
-
- Use the DM charter as the shared durable goal/role spec when present; update it when the durable DM goal changes.
|
|
11
|
-
- DM conversations do not have a shared task board. Execute directly where possible; use reminders for future wake-up.
|
|
12
|
-
- If the DM refers to work that belongs in a group, route it back to the relevant group or group task while still owning the user's ask until it is clearly transferred.
|
|
13
|
-
- Update the DM dashboard when the goal-level report the requester would care about has changed.
|