ticlawk 0.1.16-dev.3 → 0.1.16-dev.31
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 +14 -2
- package/bin/ticlawk.mjs +207 -25
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +293 -70
- package/src/adapters/ticlawk/credentials.mjs +41 -1
- package/src/adapters/ticlawk/index.mjs +199 -199
- package/src/adapters/ticlawk/wake-client.mjs +1 -1
- package/src/cli/agent-commands.mjs +607 -37
- package/src/core/agent-cli-handlers.mjs +449 -20
- package/src/core/agent-home.mjs +86 -10
- package/src/core/argv.mjs +11 -1
- package/src/core/events/worker-events.mjs +32 -36
- package/src/core/http.mjs +126 -0
- package/src/core/runtime-env.mjs +7 -0
- package/src/core/runtime-support.mjs +108 -107
- package/src/migrate/write-initial-memory.mjs +5 -5
- package/src/runtimes/_shared/agent-handbook.mjs +45 -0
- package/src/runtimes/_shared/brand.mjs +2 -0
- package/src/runtimes/_shared/goal-task-protocol.mjs +50 -0
- package/src/runtimes/_shared/handbook/BASICS.md +27 -0
- package/src/runtimes/_shared/handbook/COLLABORATION.md +37 -0
- package/src/runtimes/_shared/handbook/COMMUNICATION.md +50 -0
- package/src/runtimes/_shared/handbook/DM_SCOPE.md +13 -0
- package/src/runtimes/_shared/handbook/GOAL_AUTHORITY.md +43 -0
- package/src/runtimes/_shared/handbook/GOAL_TASK_CORE.md +43 -0
- package/src/runtimes/_shared/handbook/GROUP_ADMIN_SCOPE.md +21 -0
- package/src/runtimes/_shared/handbook/GROUP_MEMBER_SCOPE.md +15 -0
- package/src/runtimes/_shared/handbook/SURFACES.md +41 -0
- package/src/runtimes/_shared/handbook/TASK_WORKER.md +14 -0
- package/src/runtimes/_shared/standing-prompt.mjs +111 -262
- package/src/runtimes/_shared/wake-prompt.mjs +261 -0
- package/src/runtimes/claude-code/index.mjs +34 -127
- package/src/runtimes/claude-code/session.mjs +2 -7
- package/src/runtimes/codex/index.mjs +117 -54
- package/src/runtimes/codex/session.mjs +2 -12
- package/src/runtimes/openclaw/index.mjs +16 -26
- package/src/runtimes/opencode/index.mjs +45 -66
- package/src/runtimes/opencode/session.mjs +12 -12
- package/src/runtimes/pi/index.mjs +42 -60
- package/src/runtimes/pi/session.mjs +9 -6
- package/src/adapters/ticlawk/cards.mjs +0 -149
- package/src/core/media/outbound.mjs +0 -163
package/src/core/argv.mjs
CHANGED
|
@@ -29,7 +29,17 @@ export function parseOptionArgs(argv = []) {
|
|
|
29
29
|
const value = inlineValue !== undefined
|
|
30
30
|
? inlineValue
|
|
31
31
|
: argv[i + 1] && !argv[i + 1].startsWith('-') ? argv[++i] : true;
|
|
32
|
-
|
|
32
|
+
// Repeated flags collect into an array so `--attach a --attach b`
|
|
33
|
+
// surfaces as ['a','b'] to callers that expect repeatable input
|
|
34
|
+
// (--attach on message send, --member on group create, etc).
|
|
35
|
+
// First occurrence stays scalar so single-value callers don't
|
|
36
|
+
// have to learn array-or-string.
|
|
37
|
+
if (Object.prototype.hasOwnProperty.call(args, rawKey)) {
|
|
38
|
+
const existing = args[rawKey];
|
|
39
|
+
args[rawKey] = Array.isArray(existing) ? [...existing, value] : [existing, value];
|
|
40
|
+
} else {
|
|
41
|
+
args[rawKey] = value;
|
|
42
|
+
}
|
|
33
43
|
continue;
|
|
34
44
|
}
|
|
35
45
|
args._.push(arg);
|
|
@@ -1,22 +1,9 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
|
|
3
1
|
const workerEventSeq = new Map();
|
|
4
|
-
const DELTA_LOG_PREVIEW_CHARS = 48;
|
|
5
2
|
|
|
6
3
|
function shortId(value) {
|
|
7
4
|
return value ? String(value).slice(0, 8) : null;
|
|
8
5
|
}
|
|
9
6
|
|
|
10
|
-
function hashText(text) {
|
|
11
|
-
return createHash('sha1').update(String(text)).digest('hex').slice(0, 12);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function previewDelta(text) {
|
|
15
|
-
const normalized = String(text).replace(/\n/g, '\\n');
|
|
16
|
-
if (normalized.length <= DELTA_LOG_PREVIEW_CHARS) return normalized;
|
|
17
|
-
return normalized.slice(0, DELTA_LOG_PREVIEW_CHARS) + '…';
|
|
18
|
-
}
|
|
19
|
-
|
|
20
7
|
function nextWorkerEventMeta({ agent, sessionId, turnId }) {
|
|
21
8
|
const key = `${agent}:${sessionId || ''}:${turnId || 'session'}`;
|
|
22
9
|
const seq = (workerEventSeq.get(key) || 0) + 1;
|
|
@@ -37,6 +24,16 @@ export async function emitWorkerEvent({ adapter, binding, agent, sessionId, cwd
|
|
|
37
24
|
if (!sessionId || typeof adapter.emitEvent !== 'function') return;
|
|
38
25
|
const eventName = event?.worker_event_name || event?.hook_event_name || 'unknown';
|
|
39
26
|
const logicalTurnId = replyToMessageId || turnId || event?.turn_id || null;
|
|
27
|
+
if (eventName === 'worker.message.delta') {
|
|
28
|
+
logger?.debugLog?.('events', 'delta.drop', {
|
|
29
|
+
bindingId: binding.id,
|
|
30
|
+
agent,
|
|
31
|
+
sessionId: shortId(sessionId),
|
|
32
|
+
turnId: shortId(logicalTurnId),
|
|
33
|
+
chars: typeof event?.delta === 'string' ? event.delta.length : null,
|
|
34
|
+
});
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
40
37
|
const { key, seq, originTs } = nextWorkerEventMeta({ agent, sessionId, turnId: logicalTurnId });
|
|
41
38
|
const enrichedEvent = {
|
|
42
39
|
...event,
|
|
@@ -45,29 +42,14 @@ export async function emitWorkerEvent({ adapter, binding, agent, sessionId, cwd
|
|
|
45
42
|
event_seq: seq,
|
|
46
43
|
origin_ts: originTs,
|
|
47
44
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
seq,
|
|
57
|
-
chars: event.delta.length,
|
|
58
|
-
hash: enrichedEvent.delta_hash,
|
|
59
|
-
preview: previewDelta(event.delta),
|
|
60
|
-
});
|
|
61
|
-
} else {
|
|
62
|
-
logger?.debugLog?.('events', 'event.recv', {
|
|
63
|
-
bindingId: binding.id,
|
|
64
|
-
agent,
|
|
65
|
-
sessionId: shortId(sessionId),
|
|
66
|
-
turnId: shortId(logicalTurnId),
|
|
67
|
-
seq,
|
|
68
|
-
eventName,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
45
|
+
logger?.debugLog?.('events', 'event.recv', {
|
|
46
|
+
bindingId: binding.id,
|
|
47
|
+
agent,
|
|
48
|
+
sessionId: shortId(sessionId),
|
|
49
|
+
turnId: shortId(logicalTurnId),
|
|
50
|
+
seq,
|
|
51
|
+
eventName,
|
|
52
|
+
});
|
|
71
53
|
await adapter.emitEvent(binding, {
|
|
72
54
|
agent,
|
|
73
55
|
sessionId,
|
|
@@ -78,3 +60,17 @@ export async function emitWorkerEvent({ adapter, binding, agent, sessionId, cwd
|
|
|
78
60
|
clearWorkerEventMeta(key);
|
|
79
61
|
}
|
|
80
62
|
}
|
|
63
|
+
|
|
64
|
+
export function emitWorkerEventBestEffort(args) {
|
|
65
|
+
void emitWorkerEvent(args).catch((err) => {
|
|
66
|
+
const event = args?.event || {};
|
|
67
|
+
const eventName = event.worker_event_name || event.hook_event_name || 'unknown';
|
|
68
|
+
args?.logger?.debugError?.('events', 'event.best-effort-failed', {
|
|
69
|
+
bindingId: args?.binding?.id || null,
|
|
70
|
+
agent: args?.agent || null,
|
|
71
|
+
sessionId: shortId(args?.sessionId),
|
|
72
|
+
eventName,
|
|
73
|
+
error: err?.message || 'unknown error',
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
package/src/core/http.mjs
CHANGED
|
@@ -3,6 +3,25 @@ import {
|
|
|
3
3
|
handleAttachmentUpload,
|
|
4
4
|
handleAttachmentView,
|
|
5
5
|
handleGroupCreate,
|
|
6
|
+
handleWorkstreamCharterGet,
|
|
7
|
+
handleWorkstreamCharterSet,
|
|
8
|
+
handleWorkstreamCreate,
|
|
9
|
+
handleWorkstreamDelete,
|
|
10
|
+
handleWorkstreamList,
|
|
11
|
+
handleAgentList,
|
|
12
|
+
handleAgentCreate,
|
|
13
|
+
handleAgentDelete,
|
|
14
|
+
handleWorkstreamDashboardSet,
|
|
15
|
+
handleWorkstreamDashboardGet,
|
|
16
|
+
handleCredentialRequest,
|
|
17
|
+
handleBriefingPublish,
|
|
18
|
+
handleBriefingGet,
|
|
19
|
+
handleServiceCreate,
|
|
20
|
+
handleServiceUpdate,
|
|
21
|
+
handleServiceDelete,
|
|
22
|
+
handleServiceList,
|
|
23
|
+
handleServiceInfo,
|
|
24
|
+
handleServiceCall,
|
|
6
25
|
handleGroupMembers,
|
|
7
26
|
handleGroupMembersAdd,
|
|
8
27
|
handleGroupMembersRemove,
|
|
@@ -11,6 +30,7 @@ import {
|
|
|
11
30
|
handleMessageRead,
|
|
12
31
|
handleMessageSearch,
|
|
13
32
|
handleMessageSend,
|
|
33
|
+
handleProfileAvatarUpload,
|
|
14
34
|
handleProfileShow,
|
|
15
35
|
handleProfileUpdate,
|
|
16
36
|
handleReminderCancel,
|
|
@@ -148,6 +168,12 @@ export function startLocalHttpServer({ port, adapter, ctx }) {
|
|
|
148
168
|
const r = await handleProfileUpdate(req, body, cliCtx);
|
|
149
169
|
return writeJson(res, r.status, r.body);
|
|
150
170
|
}
|
|
171
|
+
if (urlNoQuery === '/agent/profile/avatar' && method === 'POST') {
|
|
172
|
+
const body = await readJsonBody(req);
|
|
173
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
174
|
+
const r = await handleProfileAvatarUpload(req, body, cliCtx);
|
|
175
|
+
return writeJson(res, r.status, r.body);
|
|
176
|
+
}
|
|
151
177
|
if (urlNoQuery === '/agent/attachment/upload' && method === 'POST') {
|
|
152
178
|
const body = await readJsonBody(req);
|
|
153
179
|
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
@@ -216,6 +242,106 @@ export function startLocalHttpServer({ port, adapter, ctx }) {
|
|
|
216
242
|
const r = await handleServerInfo(req, parseQuery(req.url || ''), cliCtx);
|
|
217
243
|
return writeJson(res, r.status, r.body);
|
|
218
244
|
}
|
|
245
|
+
if (urlNoQuery === '/agent/workstream/charter/get' && method === 'GET') {
|
|
246
|
+
const r = await handleWorkstreamCharterGet(req, parseQuery(req.url || ''), cliCtx);
|
|
247
|
+
return writeJson(res, r.status, r.body);
|
|
248
|
+
}
|
|
249
|
+
if (urlNoQuery === '/agent/workstream/charter/set' && method === 'POST') {
|
|
250
|
+
const body = await readJsonBody(req);
|
|
251
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
252
|
+
const r = await handleWorkstreamCharterSet(req, body, cliCtx);
|
|
253
|
+
return writeJson(res, r.status, r.body);
|
|
254
|
+
}
|
|
255
|
+
if (urlNoQuery === '/agent/workstream/create' && method === 'POST') {
|
|
256
|
+
const body = await readJsonBody(req);
|
|
257
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
258
|
+
const r = await handleWorkstreamCreate(req, body, cliCtx);
|
|
259
|
+
return writeJson(res, r.status, r.body);
|
|
260
|
+
}
|
|
261
|
+
if (urlNoQuery === '/agent/workstream/delete' && method === 'POST') {
|
|
262
|
+
const body = await readJsonBody(req);
|
|
263
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
264
|
+
const r = await handleWorkstreamDelete(req, body, cliCtx);
|
|
265
|
+
return writeJson(res, r.status, r.body);
|
|
266
|
+
}
|
|
267
|
+
if (urlNoQuery === '/agent/workstream/list' && method === 'GET') {
|
|
268
|
+
const r = await handleWorkstreamList(req, parseQuery(req.url || ''), cliCtx);
|
|
269
|
+
return writeJson(res, r.status, r.body);
|
|
270
|
+
}
|
|
271
|
+
if (urlNoQuery === '/agent/agent/list' && method === 'GET') {
|
|
272
|
+
const r = await handleAgentList(req, parseQuery(req.url || ''), cliCtx);
|
|
273
|
+
return writeJson(res, r.status, r.body);
|
|
274
|
+
}
|
|
275
|
+
if (urlNoQuery === '/agent/agent/create' && method === 'POST') {
|
|
276
|
+
const body = await readJsonBody(req);
|
|
277
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
278
|
+
const r = await handleAgentCreate(req, body, cliCtx);
|
|
279
|
+
return writeJson(res, r.status, r.body);
|
|
280
|
+
}
|
|
281
|
+
if (urlNoQuery === '/agent/agent/delete' && method === 'POST') {
|
|
282
|
+
const body = await readJsonBody(req);
|
|
283
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
284
|
+
const r = await handleAgentDelete(req, body, cliCtx);
|
|
285
|
+
return writeJson(res, r.status, r.body);
|
|
286
|
+
}
|
|
287
|
+
if (urlNoQuery === '/agent/dashboard/set' && method === 'POST') {
|
|
288
|
+
const body = await readJsonBody(req);
|
|
289
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
290
|
+
const r = await handleWorkstreamDashboardSet(req, body, cliCtx);
|
|
291
|
+
return writeJson(res, r.status, r.body);
|
|
292
|
+
}
|
|
293
|
+
if (urlNoQuery === '/agent/dashboard/get' && method === 'GET') {
|
|
294
|
+
const r = await handleWorkstreamDashboardGet(req, parseQuery(req.url || ''), cliCtx);
|
|
295
|
+
return writeJson(res, r.status, r.body);
|
|
296
|
+
}
|
|
297
|
+
if (urlNoQuery === '/agent/briefing/publish' && method === 'POST') {
|
|
298
|
+
const body = await readJsonBody(req);
|
|
299
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
300
|
+
const r = await handleBriefingPublish(req, body, cliCtx);
|
|
301
|
+
return writeJson(res, r.status, r.body);
|
|
302
|
+
}
|
|
303
|
+
if (urlNoQuery === '/agent/briefing/get' && method === 'GET') {
|
|
304
|
+
const r = await handleBriefingGet(req, parseQuery(req.url || ''), cliCtx);
|
|
305
|
+
return writeJson(res, r.status, r.body);
|
|
306
|
+
}
|
|
307
|
+
if (urlNoQuery === '/agent/credential/request' && method === 'POST') {
|
|
308
|
+
const body = await readJsonBody(req);
|
|
309
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
310
|
+
const r = await handleCredentialRequest(req, body, cliCtx);
|
|
311
|
+
return writeJson(res, r.status, r.body);
|
|
312
|
+
}
|
|
313
|
+
if (urlNoQuery === '/agent/service/create' && method === 'POST') {
|
|
314
|
+
const body = await readJsonBody(req);
|
|
315
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
316
|
+
const r = await handleServiceCreate(req, body, cliCtx);
|
|
317
|
+
return writeJson(res, r.status, r.body);
|
|
318
|
+
}
|
|
319
|
+
if (urlNoQuery === '/agent/service/update' && method === 'POST') {
|
|
320
|
+
const body = await readJsonBody(req);
|
|
321
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
322
|
+
const r = await handleServiceUpdate(req, body, cliCtx);
|
|
323
|
+
return writeJson(res, r.status, r.body);
|
|
324
|
+
}
|
|
325
|
+
if (urlNoQuery === '/agent/service/delete' && method === 'POST') {
|
|
326
|
+
const body = await readJsonBody(req);
|
|
327
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
328
|
+
const r = await handleServiceDelete(req, body, cliCtx);
|
|
329
|
+
return writeJson(res, r.status, r.body);
|
|
330
|
+
}
|
|
331
|
+
if (urlNoQuery === '/agent/service/list' && method === 'GET') {
|
|
332
|
+
const r = await handleServiceList(req, parseQuery(req.url || ''), cliCtx);
|
|
333
|
+
return writeJson(res, r.status, r.body);
|
|
334
|
+
}
|
|
335
|
+
if (urlNoQuery === '/agent/service/info' && method === 'GET') {
|
|
336
|
+
const r = await handleServiceInfo(req, parseQuery(req.url || ''), cliCtx);
|
|
337
|
+
return writeJson(res, r.status, r.body);
|
|
338
|
+
}
|
|
339
|
+
if (urlNoQuery === '/agent/service/call' && method === 'POST') {
|
|
340
|
+
const body = await readJsonBody(req);
|
|
341
|
+
if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
|
|
342
|
+
const r = await handleServiceCall(req, body, cliCtx);
|
|
343
|
+
return writeJson(res, r.status, r.body);
|
|
344
|
+
}
|
|
219
345
|
|
|
220
346
|
writeJson(res, 404, { error: 'not found' });
|
|
221
347
|
} catch (err) {
|
package/src/core/runtime-env.mjs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const STRIPPED_KEYS = new Set([
|
|
16
16
|
'TICLAWK_CONNECTOR_API_KEY',
|
|
17
17
|
'TICLAWK_CONNECTOR_WS_URL',
|
|
18
|
+
'TICLAWK_CREDENTIAL_NAMES',
|
|
18
19
|
'TICLAWK_SETUP_CODE',
|
|
19
20
|
]);
|
|
20
21
|
|
|
@@ -35,11 +36,17 @@ export function buildAgentRuntimeEnv({
|
|
|
35
36
|
sessionId,
|
|
36
37
|
hostId,
|
|
37
38
|
daemonUrl,
|
|
39
|
+
conversationId,
|
|
40
|
+
messageId,
|
|
41
|
+
target,
|
|
38
42
|
} = {}) {
|
|
39
43
|
const out = {};
|
|
40
44
|
if (agentId) out.TICLAWK_RUNTIME_AGENT_ID = String(agentId);
|
|
41
45
|
if (sessionId) out.TICLAWK_RUNTIME_SESSION_ID = String(sessionId);
|
|
42
46
|
if (hostId) out.TICLAWK_RUNTIME_HOST_ID = String(hostId);
|
|
47
|
+
if (conversationId) out.TICLAWK_RUNTIME_CONVERSATION_ID = String(conversationId);
|
|
48
|
+
if (messageId) out.TICLAWK_RUNTIME_MESSAGE_ID = String(messageId);
|
|
49
|
+
if (target) out.TICLAWK_RUNTIME_TARGET = String(target);
|
|
43
50
|
out.TICLAWK_RUNTIME_DAEMON_URL = daemonUrl || 'http://127.0.0.1:8741';
|
|
44
51
|
return out;
|
|
45
52
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getStreamingMode } from './config.mjs';
|
|
2
2
|
import { getAgentHome } from './agent-home.mjs';
|
|
3
|
+
import { debugError } from './logger.mjs';
|
|
3
4
|
const ERROR_MAX_CHARS = 500;
|
|
4
|
-
const
|
|
5
|
-
const DEFAULT_DELTA_FLUSH_CHARS = 64;
|
|
5
|
+
const MAX_SCOPED_RUNTIME_SESSIONS = 50;
|
|
6
6
|
|
|
7
7
|
function truncateError(text) {
|
|
8
8
|
if (!text) return null;
|
|
@@ -42,36 +42,6 @@ export function shouldStreamRuntime(runtimeName, runtime) {
|
|
|
42
42
|
return Boolean(runtime?.runTurnStream) && getStreamingMode(runtimeName);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
export async function sendAdapterMessage(adapter, binding, payload) {
|
|
46
|
-
await adapter.send(binding, payload);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Record the runtime's final turn output as activity (NOT chat).
|
|
51
|
-
*
|
|
52
|
-
* Previously called `sendResult` and treated as "the chat reply path".
|
|
53
|
-
* After the group-chat upgrade, chat is produced exclusively by the
|
|
54
|
-
* agent invoking `ticlawk message send` via the CLI. The runtime's
|
|
55
|
-
* raw final output is still surfaced for trajectory/debug UI, but it
|
|
56
|
-
* no longer materializes as a `messages` row — the trigger
|
|
57
|
-
* `project_agent_event` was updated in PR-2b to drop the chat
|
|
58
|
-
* projection.
|
|
59
|
-
*
|
|
60
|
-
* Renamed so the call sites read self-evidently: "record activity"
|
|
61
|
-
* never reads as "send a chat message".
|
|
62
|
-
*/
|
|
63
|
-
export async function recordActivity(adapter, binding, inbound, result) {
|
|
64
|
-
if (!result?.text && (!result?.mediaUrls || result.mediaUrls.length === 0)) return;
|
|
65
|
-
await sendAdapterMessage(adapter, binding, {
|
|
66
|
-
type: 'assistant',
|
|
67
|
-
text: result.text || '',
|
|
68
|
-
media: result.media || [],
|
|
69
|
-
turnId: inbound.messageId || result?.turnId || null,
|
|
70
|
-
replyToMessageId: inbound.messageId || null,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
45
|
export async function reportSubprocessFailure({ adapter, binding, inbound, runtimeName, info }) {
|
|
76
46
|
if (!info || info.ok) return;
|
|
77
47
|
const seconds = ((info.durationMs || 0) / 1000).toFixed(1);
|
|
@@ -104,12 +74,21 @@ export async function reportSubprocessFailure({ adapter, binding, inbound, runti
|
|
|
104
74
|
// skips fan-out to member-role agents, breaking what would otherwise
|
|
105
75
|
// be a failure→fan-out→failure cascade when several agents share a
|
|
106
76
|
// broken runtime.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
77
|
+
try {
|
|
78
|
+
await adapter.postAgentReply(binding, {
|
|
79
|
+
conversationId: inbound?.conversationId || null,
|
|
80
|
+
text,
|
|
81
|
+
replyToMessageId: inbound?.messageId || null,
|
|
82
|
+
visibility: 'admin',
|
|
83
|
+
});
|
|
84
|
+
} catch (err) {
|
|
85
|
+
debugError('runtime', 'failure-notice.failed', {
|
|
86
|
+
agentId: binding?.id || null,
|
|
87
|
+
runtimeName,
|
|
88
|
+
messageId: inbound?.messageId || null,
|
|
89
|
+
error: err?.message || 'unknown error',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
113
92
|
}
|
|
114
93
|
|
|
115
94
|
export function terminalRuntimeFailure(reason = 'runtime failure') {
|
|
@@ -135,80 +114,102 @@ export async function updateBindingRuntimeMeta(ctx, binding, runtimeMetaPatch, e
|
|
|
135
114
|
});
|
|
136
115
|
}
|
|
137
116
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
if (!
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
117
|
+
function readScopedSessionKey(inbound = {}) {
|
|
118
|
+
const conversationId = String(inbound?.conversationId || '').trim();
|
|
119
|
+
if (!conversationId) return '';
|
|
120
|
+
const threadRoot = String(inbound?.raw?.thread_root_message_id || '').trim();
|
|
121
|
+
return threadRoot ? `${conversationId}:thread:${threadRoot}` : conversationId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeScopedRuntimeSessions(value) {
|
|
125
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
126
|
+
const out = {};
|
|
127
|
+
for (const [key, session] of Object.entries(value)) {
|
|
128
|
+
if (!key || !session || typeof session !== 'object' || Array.isArray(session)) continue;
|
|
129
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
130
|
+
if (!sessionId) continue;
|
|
131
|
+
out[key] = {
|
|
132
|
+
sessionId,
|
|
133
|
+
path: typeof session.path === 'string' ? session.path : null,
|
|
134
|
+
lastRotatedAt: typeof session.lastRotatedAt === 'string' ? session.lastRotatedAt : null,
|
|
135
|
+
updatedAt: typeof session.updatedAt === 'string' ? session.updatedAt : null,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
153
140
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
141
|
+
function pruneScopedRuntimeSessions(sessions) {
|
|
142
|
+
const entries = Object.entries(sessions);
|
|
143
|
+
if (entries.length <= MAX_SCOPED_RUNTIME_SESSIONS) return sessions;
|
|
144
|
+
return Object.fromEntries(entries
|
|
145
|
+
.sort(([, a], [, b]) => {
|
|
146
|
+
const aTs = Date.parse(a?.updatedAt || a?.lastRotatedAt || '') || 0;
|
|
147
|
+
const bTs = Date.parse(b?.updatedAt || b?.lastRotatedAt || '') || 0;
|
|
148
|
+
return bTs - aTs;
|
|
149
|
+
})
|
|
150
|
+
.slice(0, MAX_SCOPED_RUNTIME_SESSIONS));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function resolveRuntimeSessionScope(meta = {}, inbound = {}) {
|
|
154
|
+
const key = readScopedSessionKey(inbound);
|
|
155
|
+
if (!key) {
|
|
156
|
+
return {
|
|
157
|
+
key: '',
|
|
158
|
+
sessions: {},
|
|
159
|
+
sessionId: meta.sessionId || null,
|
|
160
|
+
path: meta.path || null,
|
|
161
|
+
lastRotatedAt: meta.lastRotatedAt || null,
|
|
162
|
+
shouldRotate: !meta.sessionId || Boolean(meta.rotatePending),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const sessions = meta.rotatePending ? {} : normalizeScopedRuntimeSessions(meta.conversationSessions);
|
|
167
|
+
const scoped = sessions[key] || {};
|
|
168
|
+
return {
|
|
169
|
+
key,
|
|
170
|
+
sessions,
|
|
171
|
+
sessionId: scoped.sessionId || null,
|
|
172
|
+
path: scoped.path || null,
|
|
173
|
+
lastRotatedAt: scoped.lastRotatedAt || null,
|
|
174
|
+
shouldRotate: !scoped.sessionId || Boolean(meta.rotatePending),
|
|
165
175
|
};
|
|
176
|
+
}
|
|
166
177
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
export function buildRuntimeSessionMetaPatch(meta = {}, scope = {}, result = {}) {
|
|
179
|
+
const now = new Date().toISOString();
|
|
180
|
+
const scoped = Boolean(scope.key);
|
|
181
|
+
const sessionId = result?.sessionId || scope.sessionId || (scoped ? null : meta.sessionId || null);
|
|
182
|
+
const path = result?.path || scope.path || (scoped ? null : meta.path || null);
|
|
183
|
+
const lastRotatedAt = scope.shouldRotate
|
|
184
|
+
? now
|
|
185
|
+
: (scope.lastRotatedAt || meta.lastRotatedAt || now);
|
|
186
|
+
|
|
187
|
+
if (!scoped) {
|
|
188
|
+
return {
|
|
189
|
+
sessionId,
|
|
190
|
+
...(path !== undefined ? { path } : {}),
|
|
191
|
+
rotatePending: false,
|
|
192
|
+
lastRotatedAt,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const sessions = {
|
|
197
|
+
...(scope.sessions || {}),
|
|
174
198
|
};
|
|
199
|
+
if (sessionId) {
|
|
200
|
+
sessions[scope.key] = {
|
|
201
|
+
sessionId,
|
|
202
|
+
path,
|
|
203
|
+
lastRotatedAt,
|
|
204
|
+
updatedAt: now,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
175
207
|
|
|
176
208
|
return {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
sessionId: meta.sessionId || null,
|
|
183
|
-
turnId: meta.turnId || null,
|
|
184
|
-
cwd: meta.cwd || '',
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
const sameContext = !pendingMeta
|
|
188
|
-
|| (
|
|
189
|
-
pendingMeta.sessionId === nextMeta.sessionId
|
|
190
|
-
&& pendingMeta.turnId === nextMeta.turnId
|
|
191
|
-
&& pendingMeta.cwd === nextMeta.cwd
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
if (!sameContext && buffer) {
|
|
195
|
-
void startFlush();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
pendingMeta = nextMeta;
|
|
199
|
-
buffer += normalized;
|
|
200
|
-
|
|
201
|
-
if (buffer.length >= flushChars) {
|
|
202
|
-
void startFlush();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
scheduleFlush();
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
async flush() {
|
|
210
|
-
clearTimer();
|
|
211
|
-
await startFlush();
|
|
212
|
-
},
|
|
209
|
+
sessionId,
|
|
210
|
+
path,
|
|
211
|
+
conversationSessions: pruneScopedRuntimeSessions(sessions),
|
|
212
|
+
rotatePending: false,
|
|
213
|
+
lastRotatedAt,
|
|
213
214
|
};
|
|
214
215
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Seed the
|
|
3
|
-
//
|
|
2
|
+
// Seed the per-agent home + MEMORY.md for every paired agent in the
|
|
3
|
+
// linked Supabase project.
|
|
4
4
|
//
|
|
5
5
|
// ~/.ticlawk/agents/<agent_id>/MEMORY.md
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// Replaces the Phase-B variant that wrote MEMORY.md into each agent's
|
|
8
|
+
// project workdir. Each agent now has its own home dir as cwd, with
|
|
9
|
+
// MEMORY.md living at the root of that home.
|
|
10
10
|
//
|
|
11
11
|
// Usage:
|
|
12
12
|
// node src/migrate/write-initial-memory.mjs # dry-run
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
}
|