akemon 0.3.5 → 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.
- package/DATA_POLICY.md +128 -0
- package/README.md +156 -19
- package/TRADEMARK.md +74 -0
- package/dist/akemon-home.js +56 -0
- package/dist/akemon-message.js +107 -0
- package/dist/best-effort.js +8 -0
- package/dist/cli.js +1411 -132
- package/dist/cognitive-artifact-store.js +101 -0
- package/dist/cognitive-event-log.js +47 -0
- package/dist/config.js +45 -9
- package/dist/context.js +27 -6
- package/dist/core/contracts/layers.js +1 -0
- package/dist/core/contracts/permission.js +1 -0
- package/dist/core/contracts/workspace.js +1 -0
- package/dist/core-cognitive-module.js +768 -0
- package/dist/engine-peripheral.js +127 -26
- package/dist/engine-routing.js +58 -17
- package/dist/interactive-session.js +361 -0
- package/dist/local-interconnect.js +156 -0
- package/dist/local-registry.js +178 -0
- package/dist/mcp-server.js +4 -1
- package/dist/memory-proposal.js +379 -0
- package/dist/memory-recorder.js +368 -0
- package/dist/orphan-scan.js +36 -24
- package/dist/passive-reflection-cognitive-module.js +172 -0
- package/dist/peripheral-registry.js +235 -0
- package/dist/permission-audit.js +132 -0
- package/dist/relay-client.js +68 -9
- package/dist/relay-mode.js +34 -0
- package/dist/relay-peripheral.js +139 -49
- package/dist/runtime-platform.js +122 -0
- package/dist/secretariat/client.js +87 -0
- package/dist/self.js +15 -6
- package/dist/server.js +3695 -439
- package/dist/social-discovery.js +231 -0
- package/dist/software-agent-peripheral.js +314 -235
- package/dist/software-agent-result-cli.js +69 -0
- package/dist/software-agent-stream-cli.js +23 -0
- package/dist/software-agent-transport.js +177 -0
- package/dist/task-module.js +243 -0
- package/dist/task-registry.js +756 -0
- package/dist/vendor/xterm/addon-fit.js +2 -0
- package/dist/vendor/xterm/addon-search.js +2 -0
- package/dist/vendor/xterm/addon-web-links.js +2 -0
- package/dist/vendor/xterm/xterm.css +285 -0
- package/dist/vendor/xterm/xterm.js +2 -0
- package/dist/work-memory.js +339 -0
- package/dist/workbench-peripheral-guide.js +79 -0
- package/dist/workbench-session.js +1074 -0
- package/dist/workbench.html +4011 -0
- package/package.json +11 -4
- package/scripts/build.cjs +24 -0
- package/scripts/check-architecture-baseline.cjs +68 -0
- package/scripts/test.cjs +38 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export function renderSoftwareAgentRunResult(result, writers = {
|
|
2
|
+
stdout: (chunk) => process.stdout.write(chunk),
|
|
3
|
+
stderr: (chunk) => process.stderr.write(chunk),
|
|
4
|
+
}) {
|
|
5
|
+
const output = readString(result?.output);
|
|
6
|
+
if (output) {
|
|
7
|
+
writers.stdout(output);
|
|
8
|
+
if (!output.endsWith("\n"))
|
|
9
|
+
writers.stdout("\n");
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
writers.stdout(`${JSON.stringify(result, null, 2)}\n`);
|
|
13
|
+
}
|
|
14
|
+
const taskId = readString(result?.taskId) || "unknown";
|
|
15
|
+
const success = result?.success === false ? false : true;
|
|
16
|
+
const exitCode = readExitCode(result?.exitCode);
|
|
17
|
+
const durationMs = readDurationMs(result?.durationMs);
|
|
18
|
+
const parts = [`[software-agent] task ${taskId} ${success ? "finished" : "failed"}`];
|
|
19
|
+
if (exitCode !== undefined)
|
|
20
|
+
parts.push(`exit=${exitCode}`);
|
|
21
|
+
if (durationMs !== undefined)
|
|
22
|
+
parts.push(`duration=${durationMs}ms`);
|
|
23
|
+
stderrLine(writers, parts.join(" "));
|
|
24
|
+
const error = readString(result?.error);
|
|
25
|
+
if (!success && error)
|
|
26
|
+
stderrLine(writers, `[software-agent] error: ${truncateOneLine(error, 240)}`);
|
|
27
|
+
printMetadata(result, writers);
|
|
28
|
+
printNextSteps(result, taskId, writers);
|
|
29
|
+
return !success;
|
|
30
|
+
}
|
|
31
|
+
function printMetadata(result, writers) {
|
|
32
|
+
const contextSessionId = readString(result?.contextSessionId);
|
|
33
|
+
const contextPacketPath = readString(result?.contextPacketPath);
|
|
34
|
+
const workMemoryDir = readString(result?.workMemoryDir);
|
|
35
|
+
if (contextSessionId)
|
|
36
|
+
stderrLine(writers, `[software-agent] session: ${truncateOneLine(contextSessionId, 120)}`);
|
|
37
|
+
if (contextPacketPath)
|
|
38
|
+
stderrLine(writers, `[software-agent] context: ${truncateOneLine(contextPacketPath, 240)}`);
|
|
39
|
+
if (workMemoryDir)
|
|
40
|
+
stderrLine(writers, `[software-agent] work memory: ${truncateOneLine(workMemoryDir, 240)}`);
|
|
41
|
+
}
|
|
42
|
+
function printNextSteps(result, taskId, writers) {
|
|
43
|
+
const contextSessionId = readString(result?.contextSessionId);
|
|
44
|
+
const workMemoryDir = readString(result?.workMemoryDir);
|
|
45
|
+
const hints = [`akemon software-agent-tasks ${taskId}`];
|
|
46
|
+
if (contextSessionId)
|
|
47
|
+
hints.push(`akemon software-agent-sessions ${contextSessionId} --context`);
|
|
48
|
+
if (workMemoryDir)
|
|
49
|
+
hints.push("akemon work-note \"<durable work memory>\" --source codex");
|
|
50
|
+
stderrLine(writers, `[software-agent] next: ${hints.join(" | ")}`);
|
|
51
|
+
}
|
|
52
|
+
function stderrLine(writers, line) {
|
|
53
|
+
writers.stderr(`${line}\n`);
|
|
54
|
+
}
|
|
55
|
+
function readString(value) {
|
|
56
|
+
return typeof value === "string" && value.trim() ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
function readExitCode(value) {
|
|
59
|
+
return typeof value === "number" && Number.isInteger(value) ? value : undefined;
|
|
60
|
+
}
|
|
61
|
+
function readDurationMs(value) {
|
|
62
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
63
|
+
}
|
|
64
|
+
function truncateOneLine(value, max) {
|
|
65
|
+
const oneLine = value.replace(/\s+/g, " ").trim();
|
|
66
|
+
if (oneLine.length <= max)
|
|
67
|
+
return oneLine;
|
|
68
|
+
return `${oneLine.slice(0, Math.max(0, max - 3))}...`;
|
|
69
|
+
}
|
|
@@ -31,6 +31,7 @@ export class SoftwareAgentStreamCliRenderer {
|
|
|
31
31
|
const commandLine = readString(event.commandLine);
|
|
32
32
|
if (commandLine)
|
|
33
33
|
this.stderrLine(`[software-agent] command: ${truncateOneLine(commandLine, 160)}`);
|
|
34
|
+
this.printMetadata(event);
|
|
34
35
|
return false;
|
|
35
36
|
}
|
|
36
37
|
if (type === "stdout" && typeof event.chunk === "string") {
|
|
@@ -72,8 +73,30 @@ export class SoftwareAgentStreamCliRenderer {
|
|
|
72
73
|
if (output) {
|
|
73
74
|
this.stderrLine(`[software-agent] summary: ${truncateOneLine(output, 240)}`);
|
|
74
75
|
}
|
|
76
|
+
this.printNextSteps(event, taskId);
|
|
75
77
|
return !success;
|
|
76
78
|
}
|
|
79
|
+
printMetadata(event) {
|
|
80
|
+
const contextSessionId = readString(event.contextSessionId);
|
|
81
|
+
const contextPacketPath = readString(event.contextPacketPath);
|
|
82
|
+
const workMemoryDir = readString(event.workMemoryDir);
|
|
83
|
+
if (contextSessionId)
|
|
84
|
+
this.stderrLine(`[software-agent] session: ${truncateOneLine(contextSessionId, 120)}`);
|
|
85
|
+
if (contextPacketPath)
|
|
86
|
+
this.stderrLine(`[software-agent] context: ${truncateOneLine(contextPacketPath, 240)}`);
|
|
87
|
+
if (workMemoryDir)
|
|
88
|
+
this.stderrLine(`[software-agent] work memory: ${truncateOneLine(workMemoryDir, 240)}`);
|
|
89
|
+
}
|
|
90
|
+
printNextSteps(event, taskId) {
|
|
91
|
+
const contextSessionId = readString(event.contextSessionId);
|
|
92
|
+
const workMemoryDir = readString(event.workMemoryDir);
|
|
93
|
+
const hints = [`akemon software-agent-tasks ${taskId}`];
|
|
94
|
+
if (contextSessionId)
|
|
95
|
+
hints.push(`akemon software-agent-sessions ${contextSessionId} --context`);
|
|
96
|
+
if (workMemoryDir)
|
|
97
|
+
hints.push("akemon work-note \"<durable work memory>\" --source codex");
|
|
98
|
+
this.stderrLine(`[software-agent] next: ${hints.join(" | ")}`);
|
|
99
|
+
}
|
|
77
100
|
stderrLine(line) {
|
|
78
101
|
if (!this.stderrEndsWithNewline)
|
|
79
102
|
this.writers.stderr("\n");
|
|
@@ -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
|
+
}
|
package/dist/task-module.js
CHANGED
|
@@ -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
|
+
}
|