akemon 0.3.6 → 0.3.7

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.
Files changed (51) hide show
  1. package/DATA_POLICY.md +11 -3
  2. package/README.md +133 -21
  3. package/dist/akemon-home.js +56 -0
  4. package/dist/akemon-message.js +107 -0
  5. package/dist/best-effort.js +8 -0
  6. package/dist/cli.js +1188 -100
  7. package/dist/cognitive-artifact-store.js +101 -0
  8. package/dist/cognitive-event-log.js +47 -0
  9. package/dist/config.js +45 -9
  10. package/dist/context.js +27 -6
  11. package/dist/core/contracts/layers.js +1 -0
  12. package/dist/core/contracts/permission.js +1 -0
  13. package/dist/core/contracts/workspace.js +1 -0
  14. package/dist/core-cognitive-module.js +768 -0
  15. package/dist/engine-peripheral.js +127 -26
  16. package/dist/engine-routing.js +58 -17
  17. package/dist/interactive-session.js +361 -0
  18. package/dist/local-interconnect.js +156 -0
  19. package/dist/local-registry.js +178 -0
  20. package/dist/mcp-server.js +4 -1
  21. package/dist/memory-proposal.js +379 -0
  22. package/dist/memory-recorder.js +368 -0
  23. package/dist/orphan-scan.js +36 -24
  24. package/dist/passive-reflection-cognitive-module.js +172 -0
  25. package/dist/peripheral-registry.js +235 -0
  26. package/dist/permission-audit.js +132 -0
  27. package/dist/relay-client.js +68 -9
  28. package/dist/relay-mode.js +34 -0
  29. package/dist/relay-peripheral.js +139 -49
  30. package/dist/runtime-platform.js +122 -0
  31. package/dist/secretariat/client.js +87 -0
  32. package/dist/self.js +15 -6
  33. package/dist/server.js +3675 -512
  34. package/dist/social-discovery.js +231 -0
  35. package/dist/software-agent-peripheral.js +185 -244
  36. package/dist/software-agent-transport.js +177 -0
  37. package/dist/task-module.js +243 -0
  38. package/dist/task-registry.js +756 -0
  39. package/dist/vendor/xterm/addon-fit.js +2 -0
  40. package/dist/vendor/xterm/addon-search.js +2 -0
  41. package/dist/vendor/xterm/addon-web-links.js +2 -0
  42. package/dist/vendor/xterm/xterm.css +285 -0
  43. package/dist/vendor/xterm/xterm.js +2 -0
  44. package/dist/work-memory.js +59 -15
  45. package/dist/workbench-peripheral-guide.js +79 -0
  46. package/dist/workbench-session.js +1074 -0
  47. package/dist/workbench.html +4011 -0
  48. package/package.json +8 -3
  49. package/scripts/build.cjs +24 -0
  50. package/scripts/check-architecture-baseline.cjs +68 -0
  51. package/scripts/test.cjs +38 -0
@@ -0,0 +1,177 @@
1
+ import { spawn } from "child_process";
2
+ import { StringDecoder } from "string_decoder";
3
+ import { StreamingRedactor } from "./redaction.js";
4
+ import { shouldDetachChildProcess, terminateProcessTree } from "./runtime-platform.js";
5
+ export class CodexExecTransport {
6
+ id = "codex-exec";
7
+ kind = "codex-exec";
8
+ config;
9
+ activeChild = null;
10
+ constructor(config = {}) {
11
+ this.config = config;
12
+ }
13
+ describe(workdir) {
14
+ const { cmd, args } = buildCodexExecCommand({
15
+ command: this.config.command || "codex",
16
+ workdir,
17
+ model: this.config.model,
18
+ sandbox: this.config.sandbox || "workspace-write",
19
+ });
20
+ return {
21
+ id: this.id,
22
+ kind: this.kind,
23
+ commandLine: [cmd, ...args].join(" "),
24
+ inputMode: "stdin-prompt",
25
+ eventMode: "stdio",
26
+ sessionMode: "oneshot",
27
+ machineReadable: false,
28
+ nativeSession: false,
29
+ };
30
+ }
31
+ async run(request) {
32
+ const { cmd, args } = buildCodexExecCommand({
33
+ command: this.config.command || "codex",
34
+ workdir: request.workdir,
35
+ model: this.config.model,
36
+ sandbox: this.config.sandbox || "workspace-write",
37
+ });
38
+ const spawnImpl = this.config.spawnImpl || spawn;
39
+ const startedAt = request.startedAt || Date.now();
40
+ return new Promise((resolve) => {
41
+ let child;
42
+ try {
43
+ child = spawnImpl(cmd, args, {
44
+ cwd: request.workdir,
45
+ env: request.env,
46
+ stdio: ["pipe", "pipe", "pipe"],
47
+ detached: shouldDetachChildProcess(),
48
+ });
49
+ }
50
+ catch (err) {
51
+ resolve({
52
+ taskId: request.taskId,
53
+ stdout: "",
54
+ stderr: "",
55
+ error: err.message || String(err),
56
+ exitCode: null,
57
+ durationMs: Date.now() - startedAt,
58
+ aborted: false,
59
+ success: false,
60
+ });
61
+ return;
62
+ }
63
+ this.activeChild = child;
64
+ request.onProcessStart?.(child);
65
+ let stdout = "";
66
+ let stderr = "";
67
+ let finished = false;
68
+ let aborted = false;
69
+ const outDecoder = new StringDecoder("utf8");
70
+ const errDecoder = new StringDecoder("utf8");
71
+ const outRedactor = new StreamingRedactor();
72
+ const errRedactor = new StreamingRedactor();
73
+ const emitSafeStream = (stream, text) => {
74
+ if (!text)
75
+ return;
76
+ request.onStream?.({ taskId: request.taskId, stream, chunk: text });
77
+ };
78
+ const finish = (exitCode, error) => {
79
+ if (finished)
80
+ return;
81
+ finished = true;
82
+ request.signal?.removeEventListener("abort", onAbort);
83
+ clearTimeout(timer);
84
+ const tailOut = outDecoder.end();
85
+ const tailErr = errDecoder.end();
86
+ if (tailOut) {
87
+ stdout += tailOut;
88
+ emitSafeStream("stdout", outRedactor.push(tailOut));
89
+ }
90
+ if (tailErr) {
91
+ stderr += tailErr;
92
+ emitSafeStream("stderr", errRedactor.push(tailErr));
93
+ }
94
+ emitSafeStream("stdout", outRedactor.flush());
95
+ emitSafeStream("stderr", errRedactor.flush());
96
+ if (this.activeChild === child)
97
+ this.activeChild = null;
98
+ request.onProcessEnd?.(child);
99
+ const durationMs = Date.now() - startedAt;
100
+ const success = !error && !aborted && exitCode === 0;
101
+ resolve({
102
+ taskId: request.taskId,
103
+ stdout,
104
+ stderr,
105
+ error,
106
+ exitCode,
107
+ durationMs,
108
+ aborted,
109
+ success,
110
+ });
111
+ };
112
+ const onAbort = () => {
113
+ if (aborted || !child.pid)
114
+ return;
115
+ aborted = true;
116
+ terminateProcessTree(child.pid, { signal: "SIGTERM", forceAfterMs: 3000 });
117
+ };
118
+ const timer = setTimeout(() => {
119
+ if (!child.pid)
120
+ return;
121
+ aborted = true;
122
+ terminateProcessTree(child.pid, { signal: "SIGTERM", forceAfterMs: 3000 });
123
+ }, request.timeoutMs);
124
+ if (request.signal) {
125
+ if (request.signal.aborted)
126
+ onAbort();
127
+ else
128
+ request.signal.addEventListener("abort", onAbort, { once: true });
129
+ }
130
+ child.stdin?.on("error", () => { });
131
+ child.stdin?.write(request.prompt);
132
+ child.stdin?.end();
133
+ child.stdout?.on("data", (chunk) => {
134
+ const text = outDecoder.write(chunk);
135
+ if (!text)
136
+ return;
137
+ stdout += text;
138
+ emitSafeStream("stdout", outRedactor.push(text));
139
+ });
140
+ child.stderr?.on("data", (chunk) => {
141
+ const text = errDecoder.write(chunk);
142
+ if (!text)
143
+ return;
144
+ stderr += text;
145
+ emitSafeStream("stderr", errRedactor.push(text));
146
+ });
147
+ child.on("close", (code) => {
148
+ child.unref();
149
+ finish(code);
150
+ });
151
+ child.on("error", (err) => {
152
+ child.unref();
153
+ finish(null, err.message);
154
+ });
155
+ });
156
+ }
157
+ async reset() {
158
+ const activePid = this.activeChild?.pid;
159
+ if (activePid) {
160
+ terminateProcessTree(activePid, { signal: "SIGTERM", forceAfterMs: 3000 });
161
+ }
162
+ this.activeChild = null;
163
+ }
164
+ }
165
+ export function buildCodexExecCommand(opts) {
166
+ const args = [
167
+ "exec",
168
+ "--skip-git-repo-check",
169
+ "--color", "never",
170
+ "-s", opts.sandbox,
171
+ "-C", opts.workdir,
172
+ ];
173
+ if (opts.model)
174
+ args.push("-m", opts.model);
175
+ args.push("-");
176
+ return { cmd: opts.command, args };
177
+ }
@@ -16,6 +16,8 @@ import { buildRoleContext } from "./role-module.js";
16
16
  import { sortByQuadrant, dedupeWorkItems, computeRetryDelay } from "./task-helpers.js";
17
17
  import { updateMetrics } from "./metrics.js";
18
18
  import { downgradeForRetry } from "./engine-routing.js";
19
+ import { logBestEffortError } from "./best-effort.js";
20
+ import { upsertTaskRegistryRecord } from "./task-registry.js";
19
21
  // ---------------------------------------------------------------------------
20
22
  // Config
21
23
  // ---------------------------------------------------------------------------
@@ -153,6 +155,15 @@ export class TaskModule {
153
155
  origin: isRetry ? downgradeForRetry(rawOrigin) : rawOrigin,
154
156
  data: order,
155
157
  });
158
+ await this.recordOrderTask(order, {
159
+ status: "pending",
160
+ summary: isRetry ? `Relay order ${order.id} queued for retry.` : `Relay order ${order.id} queued.`,
161
+ data: {
162
+ origin: rawOrigin,
163
+ urgent,
164
+ retryCount: this.orderRetry.get(order.id)?.count || 0,
165
+ },
166
+ });
156
167
  }
157
168
  }
158
169
  catch { }
@@ -173,6 +184,13 @@ export class TaskModule {
173
184
  origin: rt ? downgradeForRetry("user_manual") : "user_manual",
174
185
  data: task,
175
186
  });
187
+ await this.recordUserTask(task, {
188
+ status: "pending",
189
+ summary: rt ? `Owner recurring task ${taskKey} queued for retry.` : `Owner recurring task ${taskKey} queued.`,
190
+ data: {
191
+ retryCount: rt?.count || 0,
192
+ },
193
+ });
176
194
  }
177
195
  }
178
196
  catch { }
@@ -188,6 +206,10 @@ export class TaskModule {
188
206
  origin: "platform",
189
207
  data: task,
190
208
  });
209
+ await this.recordRelayTask(task, {
210
+ status: "pending",
211
+ summary: `Relay platform task ${task.id} queued.`,
212
+ });
191
213
  }
192
214
  }
193
215
  catch { }
@@ -299,6 +321,77 @@ export class TaskModule {
299
321
  return filtered;
300
322
  }
301
323
  // ---------------------------------------------------------------------------
324
+ // Task activity projection
325
+ // ---------------------------------------------------------------------------
326
+ async recordTaskRegistry(patch) {
327
+ if (!this.ctx)
328
+ return;
329
+ try {
330
+ await upsertTaskRegistryRecord({
331
+ workdir: this.ctx.workdir,
332
+ agentName: this.ctx.agentName,
333
+ patch,
334
+ });
335
+ }
336
+ catch (error) {
337
+ logBestEffortError("task registry upsert", error);
338
+ }
339
+ }
340
+ async recordOrderTask(order, patch = {}) {
341
+ const orderId = readTaskText(order?.id) || "unknown";
342
+ const patchData = patch.data || {};
343
+ await this.recordTaskRegistry({
344
+ taskId: workTaskRegistryId("order", orderId),
345
+ source: "relay_order",
346
+ route: "relay-order",
347
+ objective: formatOrderObjective(order),
348
+ conversationId: orderConversationId(order),
349
+ ...patch,
350
+ data: {
351
+ orderId,
352
+ productId: readTaskText(order?.product_id),
353
+ productName: readTaskText(order?.product_name),
354
+ buyerName: readTaskText(order?.buyer_name),
355
+ buyerRef: readTaskText(order?.buyer_ip),
356
+ orderStatus: readTaskText(order?.status),
357
+ price: typeof order?.price === "number" ? order.price : order?.offer_price,
358
+ ...patchData,
359
+ },
360
+ });
361
+ }
362
+ async recordUserTask(task, patch = {}) {
363
+ const taskKey = readTaskText(task.id || task.title) || "unknown";
364
+ const patchData = patch.data || {};
365
+ await this.recordTaskRegistry({
366
+ taskId: workTaskRegistryId("user_task", taskKey),
367
+ source: "owner_recurring_task",
368
+ route: "user-task",
369
+ objective: truncateTaskText(task.body || task.title || taskKey, 220),
370
+ ...patch,
371
+ data: {
372
+ taskKey,
373
+ title: readTaskText(task.title),
374
+ ...patchData,
375
+ },
376
+ });
377
+ }
378
+ async recordRelayTask(task, patch = {}) {
379
+ const taskId = readTaskText(task?.id) || "unknown";
380
+ const patchData = patch.data || {};
381
+ await this.recordTaskRegistry({
382
+ taskId: workTaskRegistryId("relay_task", taskId),
383
+ source: "relay_platform_task",
384
+ route: "relay-task",
385
+ objective: truncateTaskText(task?.description || task?.body || task?.type || taskId, 220),
386
+ ...patch,
387
+ data: {
388
+ relayTaskId: taskId,
389
+ relayTaskType: readTaskText(task?.type),
390
+ ...patchData,
391
+ },
392
+ });
393
+ }
394
+ // ---------------------------------------------------------------------------
302
395
  // Execute order
303
396
  // ---------------------------------------------------------------------------
304
397
  async executeOrder(order, origin = "platform") {
@@ -309,6 +402,14 @@ export class TaskModule {
309
402
  const orderLabel = `order:${order.product_name || order.buyer_name || order.id}`;
310
403
  const orderPrice = order.price || order.offer_price || 1;
311
404
  const startTime = Date.now();
405
+ await this.recordOrderTask(order, {
406
+ status: "running",
407
+ summary: `Executing relay order ${order.id}.`,
408
+ data: {
409
+ origin,
410
+ attempt: (this.orderRetry.get(order.id)?.count || 0) + 1,
411
+ },
412
+ });
312
413
  try {
313
414
  const bios = biosPath(workdir, agentName);
314
415
  const directives = await loadDirectives(workdir, agentName);
@@ -377,6 +478,16 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
377
478
  this.orderUserWritten.delete(order.id);
378
479
  await appendMessage(workdir, agentName, orderConvId, "Agent", orderAgentMsg, "order");
379
480
  await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: duration, output_summary: (result.response || "").slice(0, 500) });
481
+ await this.recordOrderTask(order, {
482
+ status: "succeeded",
483
+ summary: `Relay order ${order.id} delivered.`,
484
+ data: {
485
+ deliveredBy: "agent",
486
+ durationMs: duration,
487
+ finalOrderStatus: readTaskText(finalStatus.status),
488
+ outputSummary: truncateTaskText(finalStatus.result_text || result.response || "", 500),
489
+ },
490
+ });
380
491
  await notifyOwner(nurl, `${agentName}: order done`, `Order ${order.id} delivered`, "default", ["package"]);
381
492
  bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice, productName: order.product_name }));
382
493
  }
@@ -390,6 +501,15 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
390
501
  this.orderUserWritten.delete(order.id);
391
502
  await appendMessage(workdir, agentName, orderConvId, "Agent", orderAgentMsg, "order");
392
503
  await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: duration, output_summary: result.response.slice(0, 500) });
504
+ await this.recordOrderTask(order, {
505
+ status: "succeeded",
506
+ summary: `Relay order ${order.id} delivered by framework fallback.`,
507
+ data: {
508
+ deliveredBy: "framework_fallback",
509
+ durationMs: duration,
510
+ outputSummary: truncateTaskText(result.response || "", 500),
511
+ },
512
+ });
393
513
  await notifyOwner(nurl, `${agentName}: order done`, `Order ${order.id}: ${result.response.slice(0, 200)}`, "default", ["package"]);
394
514
  bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice, productName: order.product_name }));
395
515
  }
@@ -409,6 +529,15 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
409
529
  const status = await relay.getOrder(order.id);
410
530
  if (status?.status === "completed") {
411
531
  this.orderRetry.delete(order.id);
532
+ await this.recordOrderTask(order, {
533
+ status: "succeeded",
534
+ summary: `Relay order ${order.id} completed despite an execution error.`,
535
+ data: {
536
+ deliveredBy: "agent",
537
+ error: err.message,
538
+ finalOrderStatus: readTaskText(status.status),
539
+ },
540
+ });
412
541
  bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice, productName: order.product_name }));
413
542
  return;
414
543
  }
@@ -426,11 +555,29 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
426
555
  await relay.extendOrder(order.id);
427
556
  }
428
557
  catch { }
558
+ await this.recordOrderTask(order, {
559
+ status: "waiting",
560
+ summary: `Relay order ${order.id} failed; retry ${current.count} scheduled.`,
561
+ data: {
562
+ error: err.message,
563
+ retryCount: current.count,
564
+ nextRetryAt: new Date(current.nextAt).toISOString(),
565
+ },
566
+ });
429
567
  }
430
568
  else {
431
569
  this.orderRetry.delete(order.id);
432
570
  this.orderUserWritten.delete(order.id);
433
571
  this.gaveUp.add(order.id);
572
+ await this.recordOrderTask(order, {
573
+ status: "failed",
574
+ summary: `Relay order ${order.id} failed after retries.`,
575
+ data: {
576
+ error: err.message,
577
+ retryCount: current.count,
578
+ canceled: true,
579
+ },
580
+ });
434
581
  bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: false, taskLabel: orderLabel }));
435
582
  try {
436
583
  await relay.cancelOrder(order.id);
@@ -450,6 +597,14 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
450
597
  const taskKey = task.id || task.title;
451
598
  console.log(`[task] Executing user task: ${taskKey}`);
452
599
  const startTime = Date.now();
600
+ await this.recordUserTask(task, {
601
+ status: "running",
602
+ summary: `Executing owner recurring task ${taskKey}.`,
603
+ data: {
604
+ origin,
605
+ attempt: (this.userTaskRetry.get(taskKey)?.count || 0) + 1,
606
+ },
607
+ });
453
608
  try {
454
609
  const bios = biosPath(workdir, agentName);
455
610
  const sd = selfDir(workdir, agentName);
@@ -491,6 +646,14 @@ Your personal directory: ${sd}/`;
491
646
  ts: localNow(), id: taskKey, type: "user_task", status: "success",
492
647
  duration_ms: duration, output_summary: (result.response || "").slice(0, 500),
493
648
  });
649
+ await this.recordUserTask(task, {
650
+ status: "succeeded",
651
+ summary: `Owner recurring task ${taskKey} completed.`,
652
+ data: {
653
+ durationMs: duration,
654
+ outputSummary: truncateTaskText(result.response || "", 500),
655
+ },
656
+ });
494
657
  this.userTaskRetry.delete(taskKey);
495
658
  const nurl = this.notifyUrl || (await loadAgentConfig(workdir, agentName)).notify_url;
496
659
  await notifyOwner(nurl, `${agentName}: ${taskKey}`, (result.response || "").slice(0, 300), "default", ["white_check_mark"]);
@@ -512,6 +675,16 @@ Your personal directory: ${sd}/`;
512
675
  ts: localNow(), id: taskKey, type: "user_task", status: "retry",
513
676
  duration_ms: duration, output_summary: "", error: err.message,
514
677
  });
678
+ await this.recordUserTask(task, {
679
+ status: "waiting",
680
+ summary: `Owner recurring task ${taskKey} failed; retry ${retry.count} scheduled.`,
681
+ data: {
682
+ durationMs: duration,
683
+ error: err.message,
684
+ retryCount: retry.count,
685
+ nextRetryAt: new Date(retry.nextAt).toISOString(),
686
+ },
687
+ });
515
688
  }
516
689
  else {
517
690
  this.userTaskRetry.delete(taskKey);
@@ -522,6 +695,15 @@ Your personal directory: ${sd}/`;
522
695
  ts: localNow(), id: taskKey, type: "user_task", status: "failed",
523
696
  duration_ms: duration, output_summary: "", error: err.message,
524
697
  });
698
+ await this.recordUserTask(task, {
699
+ status: "failed",
700
+ summary: `Owner recurring task ${taskKey} failed after retries.`,
701
+ data: {
702
+ durationMs: duration,
703
+ error: err.message,
704
+ retryCount: retry.count,
705
+ },
706
+ });
525
707
  const nurl = this.notifyUrl || (await loadAgentConfig(workdir, agentName)).notify_url;
526
708
  await notifyOwner(nurl, `${agentName}: ${taskKey} FAILED`, err.message.slice(0, 300), "high", ["x"]);
527
709
  }
@@ -538,9 +720,19 @@ Your personal directory: ${sd}/`;
538
720
  const claimed = await relay.claimTask(task.id);
539
721
  if (!claimed) {
540
722
  console.log(`[task] Failed to claim ${task.id}`);
723
+ await this.recordRelayTask(task, {
724
+ status: "waiting",
725
+ summary: `Relay platform task ${task.id} could not be claimed.`,
726
+ });
541
727
  return;
542
728
  }
543
729
  console.log(`[task] Executing relay task ${task.id} (${task.type || "?"})`);
730
+ const startTime = Date.now();
731
+ await this.recordRelayTask(task, {
732
+ status: "running",
733
+ summary: `Executing relay platform task ${task.id}.`,
734
+ data: { origin },
735
+ });
544
736
  try {
545
737
  const bios = biosPath(workdir, agentName);
546
738
  let biosContent = "";
@@ -592,10 +784,29 @@ Complete this task. Use the environment info above and tools (curl, etc.) as nee
592
784
  if (completed) {
593
785
  console.log(`[task] Completed relay task ${task.id}`);
594
786
  }
787
+ await this.recordRelayTask(task, {
788
+ status: "succeeded",
789
+ summary: completed
790
+ ? `Relay platform task ${task.id} completed.`
791
+ : `Relay platform task ${task.id} finished without relay completion confirmation.`,
792
+ data: {
793
+ completionConfirmed: !!completed,
794
+ durationMs: Date.now() - startTime,
795
+ outputSummary: truncateTaskText(result.response || "", 500),
796
+ },
797
+ });
595
798
  bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: `relay_task:${task.type || task.id}` }));
596
799
  }
597
800
  catch (err) {
598
801
  console.log(`[task] Relay task ${task.id} failed: ${err.message}`);
802
+ await this.recordRelayTask(task, {
803
+ status: "failed",
804
+ summary: `Relay platform task ${task.id} failed.`,
805
+ data: {
806
+ durationMs: Date.now() - startTime,
807
+ error: err.message,
808
+ },
809
+ });
599
810
  bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: false, taskLabel: `relay_task:${task.type || task.id}` }));
600
811
  relay.reportLog("platform_task", task.id, "failed", err.message, []);
601
812
  }
@@ -630,3 +841,35 @@ Complete this task. Use the environment info above and tools (curl, etc.) as nee
630
841
  catch { }
631
842
  }
632
843
  }
844
+ function workTaskRegistryId(kind, rawId) {
845
+ const safe = rawId
846
+ .replace(/[^A-Za-z0-9_.:-]+/g, "_")
847
+ .replace(/^\.+/, "")
848
+ .replace(/[. ]+$/g, "")
849
+ .slice(0, 140);
850
+ return `${kind}_${safe || "unknown"}`;
851
+ }
852
+ function orderConversationId(order) {
853
+ const buyerPubId = readTaskText(order?.buyer_ip) || readTaskText(order?.buyer_name) || "anonymous";
854
+ const productId = readTaskText(order?.product_id);
855
+ return `pub_${buyerPubId}${productId ? `:prod_${productId}` : ""}`;
856
+ }
857
+ function formatOrderObjective(order) {
858
+ const productName = readTaskText(order?.product_name);
859
+ const buyerTask = truncateTaskText(order?.buyer_task, 220);
860
+ if (productName && buyerTask)
861
+ return `${productName}: ${buyerTask}`;
862
+ return buyerTask || productName || `Relay order ${readTaskText(order?.id) || "unknown"}`;
863
+ }
864
+ function truncateTaskText(value, max) {
865
+ const text = readTaskText(value);
866
+ if (!text)
867
+ return "";
868
+ return text.length > max ? `${text.slice(0, Math.max(0, max - 3))}...` : text;
869
+ }
870
+ function readTaskText(value) {
871
+ if (typeof value !== "string" && typeof value !== "number")
872
+ return undefined;
873
+ const text = String(value).trim();
874
+ return text || undefined;
875
+ }