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
|
@@ -11,13 +11,17 @@
|
|
|
11
11
|
* transport to app-server or a true persistent interactive session.
|
|
12
12
|
*/
|
|
13
13
|
import { randomUUID } from "crypto";
|
|
14
|
-
import {
|
|
14
|
+
import { spawnSync } from "child_process";
|
|
15
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
16
16
|
import { isAbsolute, join, relative, resolve as resolvePath } from "path";
|
|
17
|
-
import { StringDecoder } from "string_decoder";
|
|
18
17
|
import { SIG, sig } from "./types.js";
|
|
19
18
|
import { sendTaskEnd, sendTaskStart, sendTaskStream } from "./relay-client.js";
|
|
20
|
-
import { redactSecrets
|
|
19
|
+
import { redactSecrets } from "./redaction.js";
|
|
20
|
+
import { terminateProcessTree } from "./runtime-platform.js";
|
|
21
|
+
import { actorRef } from "./akemon-message.js";
|
|
22
|
+
import { appendPermissionAuditRecordSync } from "./permission-audit.js";
|
|
23
|
+
import { logBestEffortError } from "./best-effort.js";
|
|
24
|
+
import { CodexExecTransport, } from "./software-agent-transport.js";
|
|
21
25
|
const defaultTaskRelay = {
|
|
22
26
|
sendTaskStart,
|
|
23
27
|
sendTaskStream,
|
|
@@ -78,12 +82,19 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
78
82
|
activeTaskId = null;
|
|
79
83
|
activeWorkdir = null;
|
|
80
84
|
sessionId = randomUUID();
|
|
85
|
+
transport;
|
|
81
86
|
constructor(config) {
|
|
82
87
|
this.config = {
|
|
83
88
|
...config,
|
|
84
89
|
envPolicy: normalizeSoftwareAgentEnvPolicy(config.envPolicy),
|
|
85
90
|
envAllowlist: normalizeSoftwareAgentEnvAllowlist(config.envAllowlist),
|
|
86
91
|
};
|
|
92
|
+
this.transport = config.transport || new CodexExecTransport({
|
|
93
|
+
command: config.command || "codex",
|
|
94
|
+
model: config.model,
|
|
95
|
+
sandbox: config.sandbox || "workspace-write",
|
|
96
|
+
spawnImpl: config.spawnImpl,
|
|
97
|
+
});
|
|
87
98
|
this.id = config.id || "software-agent:codex";
|
|
88
99
|
this.name = config.name || "Codex CLI Software Agent";
|
|
89
100
|
}
|
|
@@ -103,18 +114,9 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
103
114
|
async resetSession() {
|
|
104
115
|
const activePid = this.activeChild?.pid;
|
|
105
116
|
if (activePid) {
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
process.kill(processGroupId, "SIGTERM");
|
|
109
|
-
}
|
|
110
|
-
catch { }
|
|
111
|
-
setTimeout(() => {
|
|
112
|
-
try {
|
|
113
|
-
process.kill(processGroupId, "SIGKILL");
|
|
114
|
-
}
|
|
115
|
-
catch { }
|
|
116
|
-
}, 3000).unref();
|
|
117
|
+
terminateProcessTree(activePid, { signal: "SIGTERM", forceAfterMs: 3000 });
|
|
117
118
|
}
|
|
119
|
+
await this.transport.reset?.();
|
|
118
120
|
this.activeChild = null;
|
|
119
121
|
this.activeTaskId = null;
|
|
120
122
|
this.activeWorkdir = null;
|
|
@@ -127,12 +129,14 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
127
129
|
sessionId: this.sessionId,
|
|
128
130
|
activeTaskId: this.activeTaskId,
|
|
129
131
|
activeWorkdir: this.activeWorkdir,
|
|
130
|
-
busy: !!this.activeChild,
|
|
131
|
-
transport:
|
|
132
|
+
busy: !!this.activeTaskId || !!this.activeChild,
|
|
133
|
+
transport: this.transport.kind,
|
|
134
|
+
transportContract: this.transport.describe(currentWorkdir),
|
|
132
135
|
baseWorkdir: resolvePath(this.config.workdir),
|
|
133
136
|
workdirStatus: this.collectWorkdirStatus(currentWorkdir),
|
|
134
137
|
taskLedgerDir: this.config.taskLedgerDir,
|
|
135
138
|
contextSessionDir: this.config.contextSessionDir,
|
|
139
|
+
workMemoryDir: this.config.workMemoryDir,
|
|
136
140
|
environment: buildSoftwareAgentChildEnvironment({
|
|
137
141
|
policy: this.config.envPolicy,
|
|
138
142
|
allowlist: this.config.envAllowlist,
|
|
@@ -154,7 +158,17 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
154
158
|
return sig(SIG.SOFTWARE_AGENT_RESPONSE, { ...result }, this.id);
|
|
155
159
|
}
|
|
156
160
|
async sendTask(envelope, taskOptions) {
|
|
157
|
-
if (this.activeChild) {
|
|
161
|
+
if (this.activeTaskId || this.activeChild) {
|
|
162
|
+
this.writePermissionAudit({
|
|
163
|
+
envelope,
|
|
164
|
+
taskId: envelope.taskId,
|
|
165
|
+
workdir: envelope.workdir || this.config.workdir,
|
|
166
|
+
decision: {
|
|
167
|
+
result: "denied",
|
|
168
|
+
mode: "automatic",
|
|
169
|
+
reason: `Software agent busy (task=${this.activeTaskId})`,
|
|
170
|
+
},
|
|
171
|
+
});
|
|
158
172
|
throw new Error(`Software agent busy (task=${this.activeTaskId})`);
|
|
159
173
|
}
|
|
160
174
|
const { signal, observer } = normalizeSoftwareAgentTaskOptions(taskOptions);
|
|
@@ -162,7 +176,14 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
162
176
|
const contextSessionId = normalizeContextSessionId(envelope.contextSessionId) || taskId;
|
|
163
177
|
const workdirSafety = resolveWorkdirSafety(this.config.workdir, envelope.workdir || this.config.workdir, envelope.workdirSafety?.allowOutsideWorkdir || false);
|
|
164
178
|
const workdir = workdirSafety.effectiveWorkdir;
|
|
165
|
-
let effectiveEnvelope = {
|
|
179
|
+
let effectiveEnvelope = {
|
|
180
|
+
...envelope,
|
|
181
|
+
taskId,
|
|
182
|
+
contextSessionId,
|
|
183
|
+
workdir,
|
|
184
|
+
workdirSafety,
|
|
185
|
+
workMemoryDir: envelope.workMemoryDir || this.config.workMemoryDir,
|
|
186
|
+
};
|
|
166
187
|
const contextSession = this.config.contextSessionDir
|
|
167
188
|
? writeSoftwareAgentContextPacket(this.config.contextSessionDir, effectiveEnvelope)
|
|
168
189
|
: undefined;
|
|
@@ -171,30 +192,42 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
171
192
|
const prompt = contextSession
|
|
172
193
|
? buildContextPacketLaunchPrompt(effectiveEnvelope, contextSession.audit.packetPath)
|
|
173
194
|
: buildTaskEnvelopePrompt(effectiveEnvelope);
|
|
174
|
-
const
|
|
175
|
-
command: this.config.command || "codex",
|
|
176
|
-
workdir,
|
|
177
|
-
model: this.config.model,
|
|
178
|
-
sandbox: this.config.sandbox || "workspace-write",
|
|
179
|
-
});
|
|
195
|
+
const transportDescriptor = this.transport.describe(workdir);
|
|
180
196
|
const startedAt = Date.now();
|
|
181
197
|
const startedAtIso = new Date(startedAt).toISOString();
|
|
182
198
|
const relay = this.config.taskRelay || defaultTaskRelay;
|
|
183
|
-
const commandLine =
|
|
184
|
-
const spawnImpl = this.config.spawnImpl || spawn;
|
|
199
|
+
const commandLine = transportDescriptor.commandLine;
|
|
185
200
|
const timeoutMs = envelope.timeoutMs || this.config.defaultTimeoutMs || DEFAULT_TIMEOUT_MS;
|
|
186
201
|
const workdirStatus = this.collectWorkdirStatus(workdir);
|
|
202
|
+
const taskMetadata = {
|
|
203
|
+
contextSessionId: effectiveEnvelope.contextSessionId,
|
|
204
|
+
contextPacketPath: effectiveEnvelope.contextPacketPath,
|
|
205
|
+
workMemoryDir: effectiveEnvelope.workMemoryDir,
|
|
206
|
+
};
|
|
187
207
|
const childEnvironment = buildSoftwareAgentChildEnvironment({
|
|
188
208
|
policy: this.config.envPolicy,
|
|
189
209
|
allowlist: this.config.envAllowlist,
|
|
190
210
|
sourceEnv: this.config.sourceEnv,
|
|
191
211
|
});
|
|
212
|
+
this.writePermissionAudit({
|
|
213
|
+
envelope: effectiveEnvelope,
|
|
214
|
+
taskId,
|
|
215
|
+
workdir,
|
|
216
|
+
workdirSafety,
|
|
217
|
+
commandLine,
|
|
218
|
+
environment: childEnvironment.audit,
|
|
219
|
+
decision: {
|
|
220
|
+
result: "allowed",
|
|
221
|
+
mode: "owner-approved",
|
|
222
|
+
reason: "Owner local endpoint accepted the software-agent task envelope.",
|
|
223
|
+
},
|
|
224
|
+
});
|
|
192
225
|
const baseTaskRecord = () => ({
|
|
193
226
|
schemaVersion: 1,
|
|
194
227
|
taskId,
|
|
195
228
|
agentId: this.id,
|
|
196
229
|
sessionId: this.sessionId,
|
|
197
|
-
transport:
|
|
230
|
+
transport: this.transport.kind,
|
|
198
231
|
commandLine,
|
|
199
232
|
envelope: effectiveEnvelope,
|
|
200
233
|
startedAt: startedAtIso,
|
|
@@ -202,197 +235,103 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
202
235
|
contextSession: contextSession?.audit,
|
|
203
236
|
workdirStatus,
|
|
204
237
|
});
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
238
|
+
this.activeTaskId = taskId;
|
|
239
|
+
this.activeWorkdir = workdir;
|
|
240
|
+
this.writeTaskRecord({
|
|
241
|
+
...baseTaskRecord(),
|
|
242
|
+
status: "running",
|
|
243
|
+
updatedAt: startedAtIso,
|
|
244
|
+
});
|
|
245
|
+
const origin = "software_agent";
|
|
246
|
+
relay.sendTaskStart(taskId, origin, commandLine);
|
|
247
|
+
observer?.onStart?.({ taskId, origin, commandLine, ...taskMetadata });
|
|
248
|
+
this.bus?.emit(SIG.TASK_STARTED, sig(SIG.TASK_STARTED, {
|
|
249
|
+
taskId,
|
|
250
|
+
taskType: "software_agent",
|
|
251
|
+
description: effectiveEnvelope.goal,
|
|
252
|
+
peripheral: this.id,
|
|
253
|
+
sessionId: this.sessionId,
|
|
254
|
+
}, this.id));
|
|
255
|
+
let transportResult;
|
|
256
|
+
try {
|
|
257
|
+
transportResult = await this.transport.run({
|
|
258
|
+
taskId,
|
|
259
|
+
prompt,
|
|
260
|
+
workdir,
|
|
261
|
+
env: childEnvironment.env,
|
|
262
|
+
timeoutMs,
|
|
263
|
+
signal,
|
|
264
|
+
startedAt,
|
|
265
|
+
onStream: (event) => {
|
|
266
|
+
relay.sendTaskStream(event.taskId, event.stream, event.chunk);
|
|
267
|
+
observer?.onStream?.(event);
|
|
268
|
+
},
|
|
269
|
+
onProcessStart: (child) => {
|
|
270
|
+
this.activeChild = child;
|
|
271
|
+
},
|
|
272
|
+
onProcessEnd: (child) => {
|
|
273
|
+
if (this.activeChild === child)
|
|
274
|
+
this.activeChild = null;
|
|
275
|
+
},
|
|
212
276
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
this.bus?.emit(SIG.TASK_STARTED, sig(SIG.TASK_STARTED, {
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
transportResult = {
|
|
217
280
|
taskId,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
child = spawnImpl(cmd, args, {
|
|
226
|
-
cwd: workdir,
|
|
227
|
-
env: childEnvironment.env,
|
|
228
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
229
|
-
detached: true,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
catch (err) {
|
|
233
|
-
this.activeChild = null;
|
|
234
|
-
this.activeTaskId = null;
|
|
235
|
-
this.activeWorkdir = null;
|
|
236
|
-
const durationMs = Date.now() - startedAt;
|
|
237
|
-
const result = {
|
|
238
|
-
success: false,
|
|
239
|
-
taskId,
|
|
240
|
-
output: "",
|
|
241
|
-
error: err.message || String(err),
|
|
242
|
-
exitCode: null,
|
|
243
|
-
durationMs,
|
|
244
|
-
};
|
|
245
|
-
relay.sendTaskEnd(taskId, null, durationMs);
|
|
246
|
-
observer?.onEnd?.({ taskId, exitCode: null, durationMs, result: redactSecrets(result) });
|
|
247
|
-
const completedAt = new Date().toISOString();
|
|
248
|
-
this.writeTaskRecord({
|
|
249
|
-
...baseTaskRecord(),
|
|
250
|
-
status: "failed",
|
|
251
|
-
updatedAt: completedAt,
|
|
252
|
-
completedAt,
|
|
253
|
-
durationMs,
|
|
254
|
-
result,
|
|
255
|
-
stdoutSummary: summarizeText(""),
|
|
256
|
-
stderrSummary: summarizeText(result.error || ""),
|
|
257
|
-
});
|
|
258
|
-
if (contextSession) {
|
|
259
|
-
writeSoftwareAgentContextSessionState(contextSession.audit.statePath, effectiveEnvelope, result, completedAt);
|
|
260
|
-
}
|
|
261
|
-
this.bus?.emit(SIG.TASK_FAILED, sig(SIG.TASK_FAILED, result, this.id));
|
|
262
|
-
resolve(result);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
this.activeChild = child;
|
|
266
|
-
let stdout = "";
|
|
267
|
-
let stderr = "";
|
|
268
|
-
let finished = false;
|
|
269
|
-
let aborted = false;
|
|
270
|
-
const outDecoder = new StringDecoder("utf8");
|
|
271
|
-
const errDecoder = new StringDecoder("utf8");
|
|
272
|
-
const outRedactor = new StreamingRedactor();
|
|
273
|
-
const errRedactor = new StreamingRedactor();
|
|
274
|
-
const emitSafeStream = (stream, text) => {
|
|
275
|
-
if (!text)
|
|
276
|
-
return;
|
|
277
|
-
relay.sendTaskStream(taskId, stream, text);
|
|
278
|
-
observer?.onStream?.({ taskId, stream, chunk: text });
|
|
279
|
-
};
|
|
280
|
-
const finish = (exitCode, error) => {
|
|
281
|
-
if (finished)
|
|
282
|
-
return;
|
|
283
|
-
finished = true;
|
|
284
|
-
signal?.removeEventListener("abort", onAbort);
|
|
285
|
-
clearTimeout(timer);
|
|
286
|
-
const tailOut = outDecoder.end();
|
|
287
|
-
const tailErr = errDecoder.end();
|
|
288
|
-
if (tailOut) {
|
|
289
|
-
stdout += tailOut;
|
|
290
|
-
emitSafeStream("stdout", outRedactor.push(tailOut));
|
|
291
|
-
}
|
|
292
|
-
if (tailErr) {
|
|
293
|
-
stderr += tailErr;
|
|
294
|
-
emitSafeStream("stderr", errRedactor.push(tailErr));
|
|
295
|
-
}
|
|
296
|
-
emitSafeStream("stdout", outRedactor.flush());
|
|
297
|
-
emitSafeStream("stderr", errRedactor.flush());
|
|
298
|
-
const durationMs = Date.now() - startedAt;
|
|
299
|
-
this.activeChild = null;
|
|
300
|
-
this.activeTaskId = null;
|
|
301
|
-
this.activeWorkdir = null;
|
|
302
|
-
const output = stdout.trim() || stderr.trim();
|
|
303
|
-
const success = !error && !aborted && exitCode === 0;
|
|
304
|
-
const result = {
|
|
305
|
-
success,
|
|
306
|
-
taskId,
|
|
307
|
-
output,
|
|
308
|
-
error: success ? undefined : error || stderr.trim() || `codex exited with code ${exitCode}`,
|
|
309
|
-
exitCode,
|
|
310
|
-
durationMs,
|
|
311
|
-
};
|
|
312
|
-
relay.sendTaskEnd(taskId, exitCode, durationMs);
|
|
313
|
-
observer?.onEnd?.({ taskId, exitCode, durationMs, result: redactSecrets(result) });
|
|
314
|
-
const completedAt = new Date().toISOString();
|
|
315
|
-
this.writeTaskRecord({
|
|
316
|
-
...baseTaskRecord(),
|
|
317
|
-
status: success ? "completed" : "failed",
|
|
318
|
-
updatedAt: completedAt,
|
|
319
|
-
completedAt,
|
|
320
|
-
durationMs,
|
|
321
|
-
result,
|
|
322
|
-
stdoutSummary: summarizeText(stdout),
|
|
323
|
-
stderrSummary: summarizeText(stderr),
|
|
324
|
-
});
|
|
325
|
-
if (contextSession) {
|
|
326
|
-
writeSoftwareAgentContextSessionState(contextSession.audit.statePath, effectiveEnvelope, result, completedAt);
|
|
327
|
-
}
|
|
328
|
-
this.bus?.emit(success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, sig(success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, {
|
|
329
|
-
...result,
|
|
330
|
-
taskLabel: `software_agent:${this.id}`,
|
|
331
|
-
}, this.id));
|
|
332
|
-
resolve(result);
|
|
333
|
-
};
|
|
334
|
-
const onAbort = () => {
|
|
335
|
-
if (aborted || !child.pid)
|
|
336
|
-
return;
|
|
337
|
-
aborted = true;
|
|
338
|
-
try {
|
|
339
|
-
process.kill(-child.pid, "SIGTERM");
|
|
340
|
-
}
|
|
341
|
-
catch { }
|
|
342
|
-
setTimeout(() => {
|
|
343
|
-
try {
|
|
344
|
-
process.kill(-child.pid, "SIGKILL");
|
|
345
|
-
}
|
|
346
|
-
catch { }
|
|
347
|
-
}, 3000).unref();
|
|
281
|
+
stdout: "",
|
|
282
|
+
stderr: "",
|
|
283
|
+
error: err.message || String(err),
|
|
284
|
+
exitCode: null,
|
|
285
|
+
durationMs: Date.now() - startedAt,
|
|
286
|
+
aborted: false,
|
|
287
|
+
success: false,
|
|
348
288
|
};
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
});
|
|
387
|
-
child.on("close", (code) => {
|
|
388
|
-
child.unref();
|
|
389
|
-
finish(code);
|
|
390
|
-
});
|
|
391
|
-
child.on("error", (err) => {
|
|
392
|
-
child.unref();
|
|
393
|
-
finish(null, err.message);
|
|
394
|
-
});
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
this.activeChild = null;
|
|
292
|
+
this.activeTaskId = null;
|
|
293
|
+
this.activeWorkdir = null;
|
|
294
|
+
}
|
|
295
|
+
const output = transportResult.stdout.trim() || transportResult.stderr.trim();
|
|
296
|
+
const transportLabel = this.transport.kind === "codex-exec" ? "codex" : this.transport.kind;
|
|
297
|
+
const result = {
|
|
298
|
+
success: transportResult.success,
|
|
299
|
+
taskId,
|
|
300
|
+
output,
|
|
301
|
+
error: transportResult.success
|
|
302
|
+
? undefined
|
|
303
|
+
: transportResult.error || transportResult.stderr.trim() || `${transportLabel} exited with code ${transportResult.exitCode}`,
|
|
304
|
+
exitCode: transportResult.exitCode,
|
|
305
|
+
durationMs: transportResult.durationMs,
|
|
306
|
+
...taskMetadata,
|
|
307
|
+
};
|
|
308
|
+
relay.sendTaskEnd(taskId, transportResult.exitCode, transportResult.durationMs);
|
|
309
|
+
observer?.onEnd?.({
|
|
310
|
+
taskId,
|
|
311
|
+
exitCode: transportResult.exitCode,
|
|
312
|
+
durationMs: transportResult.durationMs,
|
|
313
|
+
result: redactSecrets(result),
|
|
314
|
+
...taskMetadata,
|
|
315
|
+
});
|
|
316
|
+
const completedAt = new Date().toISOString();
|
|
317
|
+
this.writeTaskRecord({
|
|
318
|
+
...baseTaskRecord(),
|
|
319
|
+
status: result.success ? "completed" : "failed",
|
|
320
|
+
updatedAt: completedAt,
|
|
321
|
+
completedAt,
|
|
322
|
+
durationMs: transportResult.durationMs,
|
|
323
|
+
result,
|
|
324
|
+
stdoutSummary: summarizeText(transportResult.stdout),
|
|
325
|
+
stderrSummary: summarizeText(transportResult.stderr || result.error || ""),
|
|
395
326
|
});
|
|
327
|
+
if (contextSession) {
|
|
328
|
+
writeSoftwareAgentContextSessionState(contextSession.audit.statePath, effectiveEnvelope, result, completedAt);
|
|
329
|
+
}
|
|
330
|
+
this.bus?.emit(result.success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, sig(result.success ? SIG.TASK_COMPLETED : SIG.TASK_FAILED, {
|
|
331
|
+
...result,
|
|
332
|
+
taskLabel: `software_agent:${this.id}`,
|
|
333
|
+
}, this.id));
|
|
334
|
+
return result;
|
|
396
335
|
}
|
|
397
336
|
writeTaskRecord(record) {
|
|
398
337
|
const dir = this.config.taskLedgerDir;
|
|
@@ -400,14 +339,54 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
400
339
|
return;
|
|
401
340
|
try {
|
|
402
341
|
mkdirSync(dir, { recursive: true });
|
|
403
|
-
|
|
404
|
-
writeFileSync(join(dir, `${safeTaskId}.json`), `${JSON.stringify(redactSecrets(record), null, 2)}\n`);
|
|
342
|
+
writeFileSync(softwareAgentTaskRecordPath(dir, record.taskId), `${JSON.stringify(redactSecrets(record), null, 2)}\n`);
|
|
405
343
|
pruneSoftwareAgentTaskRecords(dir, this.config.taskLedgerMaxRecords, record.taskId);
|
|
406
344
|
}
|
|
407
345
|
catch (err) {
|
|
408
346
|
console.error(`[software-agent] Failed to write task ledger: ${err.message || String(err)}`);
|
|
409
347
|
}
|
|
410
348
|
}
|
|
349
|
+
writePermissionAudit(input) {
|
|
350
|
+
if (!this.config.agentName)
|
|
351
|
+
return;
|
|
352
|
+
try {
|
|
353
|
+
const requesterKind = input.envelope.sourceModule === "owner-http" ? "owner" : "system";
|
|
354
|
+
appendPermissionAuditRecordSync(this.config.agentName, {
|
|
355
|
+
actionKind: "software-agent-task",
|
|
356
|
+
action: "software-agent.run",
|
|
357
|
+
requestedBy: actorRef(requesterKind, input.envelope.sourceModule || "owner", "local"),
|
|
358
|
+
performedBy: actorRef("agent", this.config.agentName, "software-agent"),
|
|
359
|
+
target: actorRef("external", this.id, "software-agent"),
|
|
360
|
+
sourceModule: input.envelope.sourceModule,
|
|
361
|
+
peripheralId: this.id,
|
|
362
|
+
riskLevel: input.envelope.riskLevel,
|
|
363
|
+
roleScope: input.envelope.roleScope,
|
|
364
|
+
memoryScope: input.envelope.memoryScope,
|
|
365
|
+
workdir: input.workdir || input.envelope.workdir,
|
|
366
|
+
projectScope: input.workdirSafety
|
|
367
|
+
? (input.workdirSafety.outsideBaseWorkdir ? "outside-workdir" : "inside-workdir")
|
|
368
|
+
: "unknown",
|
|
369
|
+
transport: "software-agent",
|
|
370
|
+
decision: input.decision,
|
|
371
|
+
allowedActions: input.envelope.allowedActions,
|
|
372
|
+
forbiddenActions: input.envelope.forbiddenActions,
|
|
373
|
+
references: {
|
|
374
|
+
taskId: input.taskId || input.envelope.taskId,
|
|
375
|
+
contextSessionId: input.envelope.contextSessionId,
|
|
376
|
+
contextPacketPath: input.envelope.contextPacketPath,
|
|
377
|
+
},
|
|
378
|
+
metadata: {
|
|
379
|
+
purpose: input.envelope.purpose,
|
|
380
|
+
commandLine: input.commandLine,
|
|
381
|
+
environment: input.environment,
|
|
382
|
+
workMemoryDir: input.envelope.workMemoryDir,
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
logBestEffortError("software-agent audit append", error);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
411
390
|
collectWorkdirStatus(workdir) {
|
|
412
391
|
const impl = this.config.gitStatusImpl || readGitWorktreeStatus;
|
|
413
392
|
return impl(workdir);
|
|
@@ -451,6 +430,9 @@ export function buildTaskEnvelopePrompt(envelope) {
|
|
|
451
430
|
`Risk level: ${envelope.riskLevel}`,
|
|
452
431
|
`Workdir: ${envelope.workdir}`,
|
|
453
432
|
];
|
|
433
|
+
if (envelope.workMemoryDir) {
|
|
434
|
+
lines.push(`Work memory directory: ${envelope.workMemoryDir}`);
|
|
435
|
+
}
|
|
454
436
|
if (envelope.contextPacketPath) {
|
|
455
437
|
lines.push(`Context packet path: ${envelope.contextPacketPath}`);
|
|
456
438
|
}
|
|
@@ -472,6 +454,20 @@ export function buildTaskEnvelopePrompt(envelope) {
|
|
|
472
454
|
lines.push(envelope.memorySummary.trim());
|
|
473
455
|
lines.push("");
|
|
474
456
|
}
|
|
457
|
+
if (envelope.workMemoryDir) {
|
|
458
|
+
lines.push("Work memory:");
|
|
459
|
+
lines.push("- This is user-owned working context for engineering/task continuity.");
|
|
460
|
+
lines.push("- You may read it with grep, direct file browsing, or semantic review as appropriate.");
|
|
461
|
+
lines.push("- You may update files under this directory when the task or user asks you to maintain work memory.");
|
|
462
|
+
lines.push("- Do not read or edit Akemon self memory as part of this software-agent task.");
|
|
463
|
+
lines.push("- For a quick append, use `akemon work-note \"<durable work memory>\" --source codex --kind note`.");
|
|
464
|
+
lines.push("");
|
|
465
|
+
}
|
|
466
|
+
if (envelope.workMemoryContext?.trim()) {
|
|
467
|
+
lines.push("Included work-memory context:");
|
|
468
|
+
lines.push(envelope.workMemoryContext.trim());
|
|
469
|
+
lines.push("");
|
|
470
|
+
}
|
|
475
471
|
if (envelope.allowedActions?.length) {
|
|
476
472
|
lines.push("Allowed actions:");
|
|
477
473
|
for (const item of envelope.allowedActions)
|
|
@@ -492,7 +488,9 @@ export function buildTaskEnvelopePrompt(envelope) {
|
|
|
492
488
|
lines.push("Instructions:");
|
|
493
489
|
lines.push("- Treat this envelope as the complete Akemon-provided context for this task.");
|
|
494
490
|
lines.push("- Do not attempt to read Akemon private memory outside the visible context above.");
|
|
491
|
+
lines.push("- Do not read or edit Akemon self memory unless the user explicitly names a normal file to inspect.");
|
|
495
492
|
lines.push("- Work only in the stated workdir unless the envelope explicitly allows otherwise.");
|
|
493
|
+
lines.push("- If you learn durable work memory, update the work memory directory or use `akemon work-note`.");
|
|
496
494
|
lines.push("- Report what changed, what you verified, and any remaining risk.");
|
|
497
495
|
return lines.join("\n");
|
|
498
496
|
}
|
|
@@ -504,6 +502,7 @@ export function buildContextPacketLaunchPrompt(envelope, contextPacketPath) {
|
|
|
504
502
|
`Akemon context session: ${envelope.contextSessionId || "(one-shot)"}`,
|
|
505
503
|
`Context packet: ${contextPacketPath}`,
|
|
506
504
|
`Workdir: ${envelope.workdir}`,
|
|
505
|
+
`Work memory directory: ${envelope.workMemoryDir || "(not configured)"}`,
|
|
507
506
|
"",
|
|
508
507
|
"Goal:",
|
|
509
508
|
envelope.goal,
|
|
@@ -512,7 +511,9 @@ export function buildContextPacketLaunchPrompt(envelope, contextPacketPath) {
|
|
|
512
511
|
"- Read the context packet first before doing repository work.",
|
|
513
512
|
"- Treat that file as the complete Akemon-provided context for this task.",
|
|
514
513
|
"- Do not read Akemon private memory outside the context packet.",
|
|
514
|
+
"- Do not read or edit Akemon self memory unless the user explicitly names a normal file to inspect.",
|
|
515
515
|
"- Work only in the stated workdir unless the packet explicitly allows otherwise.",
|
|
516
|
+
"- If you learn durable work memory, update the work memory directory or use `akemon work-note`.",
|
|
516
517
|
"- Report what changed, what you verified, and any remaining risk.",
|
|
517
518
|
].join("\n");
|
|
518
519
|
}
|
|
@@ -618,8 +619,9 @@ export function readGitWorktreeStatus(workdir) {
|
|
|
618
619
|
};
|
|
619
620
|
}
|
|
620
621
|
}
|
|
621
|
-
export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20) {
|
|
622
|
+
export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20, opts = {}) {
|
|
622
623
|
const safeLimit = normalizeTaskRecordLimit(limit);
|
|
624
|
+
const contextSessionId = opts.contextSessionId?.trim();
|
|
623
625
|
try {
|
|
624
626
|
if (!existsSync(taskLedgerDir))
|
|
625
627
|
return [];
|
|
@@ -627,6 +629,7 @@ export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20) {
|
|
|
627
629
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
628
630
|
.map((entry) => readSoftwareAgentTaskRecordFile(join(taskLedgerDir, entry.name)))
|
|
629
631
|
.filter((record) => !!record)
|
|
632
|
+
.filter((record) => !contextSessionId || record.contextSession?.sessionId === contextSessionId || record.envelope.contextSessionId === contextSessionId)
|
|
630
633
|
.sort(compareSoftwareAgentTaskRecords)
|
|
631
634
|
.slice(0, safeLimit);
|
|
632
635
|
}
|
|
@@ -635,8 +638,66 @@ export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20) {
|
|
|
635
638
|
}
|
|
636
639
|
}
|
|
637
640
|
export function readSoftwareAgentTaskRecord(taskLedgerDir, taskId) {
|
|
638
|
-
|
|
639
|
-
|
|
641
|
+
return readSoftwareAgentTaskRecordFile(softwareAgentTaskRecordPath(taskLedgerDir, taskId));
|
|
642
|
+
}
|
|
643
|
+
export function softwareAgentTaskRecordPath(taskLedgerDir, taskId) {
|
|
644
|
+
return join(taskLedgerDir, `${safeTaskFilename(taskId)}.json`);
|
|
645
|
+
}
|
|
646
|
+
export function listSoftwareAgentContextSessions(contextSessionDir, limit = 20) {
|
|
647
|
+
const safeLimit = normalizeTaskRecordLimit(limit);
|
|
648
|
+
try {
|
|
649
|
+
if (!existsSync(contextSessionDir))
|
|
650
|
+
return [];
|
|
651
|
+
return readdirSync(contextSessionDir, { withFileTypes: true })
|
|
652
|
+
.filter((entry) => entry.isDirectory())
|
|
653
|
+
.map((entry) => {
|
|
654
|
+
try {
|
|
655
|
+
return readSoftwareAgentContextSession(contextSessionDir, entry.name);
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
})
|
|
661
|
+
.filter((record) => !!record)
|
|
662
|
+
.sort(compareSoftwareAgentContextSessions)
|
|
663
|
+
.slice(0, safeLimit);
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
return [];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
export function readSoftwareAgentContextSession(contextSessionDir, sessionId, opts = {}) {
|
|
670
|
+
const safeSessionId = normalizeContextSessionId(sessionId, "sessionId");
|
|
671
|
+
if (!safeSessionId)
|
|
672
|
+
return null;
|
|
673
|
+
const sessionDir = join(contextSessionDir, safeSessionId);
|
|
674
|
+
if (!existsSync(sessionDir))
|
|
675
|
+
return null;
|
|
676
|
+
const packetPath = join(sessionDir, CONTEXT_PACKET_FILENAME);
|
|
677
|
+
const statePath = join(sessionDir, CONTEXT_SESSION_STATE_FILENAME);
|
|
678
|
+
const state = readSoftwareAgentContextSessionState(statePath);
|
|
679
|
+
const record = {
|
|
680
|
+
sessionId: safeSessionId,
|
|
681
|
+
packetPath,
|
|
682
|
+
statePath,
|
|
683
|
+
hasContextPacket: existsSync(packetPath),
|
|
684
|
+
};
|
|
685
|
+
if (state) {
|
|
686
|
+
record.updatedAt = state.updatedAt;
|
|
687
|
+
record.workMemoryDir = state.workMemoryDir;
|
|
688
|
+
record.lastTaskId = state.lastTaskId;
|
|
689
|
+
record.lastGoal = state.lastGoal;
|
|
690
|
+
record.lastResult = state.lastResult;
|
|
691
|
+
}
|
|
692
|
+
if (opts.includeContextPacket && record.hasContextPacket) {
|
|
693
|
+
try {
|
|
694
|
+
record.contextPacket = readFileSync(packetPath, "utf8");
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
record.contextPacket = "";
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return record;
|
|
640
701
|
}
|
|
641
702
|
export function pruneSoftwareAgentTaskRecords(taskLedgerDir, maxRecords = DEFAULT_TASK_LEDGER_MAX_RECORDS, preserveTaskId) {
|
|
642
703
|
const safeMaxRecords = normalizeTaskLedgerMaxRecords(maxRecords);
|
|
@@ -728,6 +789,7 @@ function writeSoftwareAgentContextSessionState(statePath, envelope, result, upda
|
|
|
728
789
|
schemaVersion: 1,
|
|
729
790
|
sessionId: envelope.contextSessionId,
|
|
730
791
|
updatedAt,
|
|
792
|
+
workMemoryDir: envelope.workMemoryDir,
|
|
731
793
|
lastTaskId: result.taskId,
|
|
732
794
|
lastGoal: envelope.goal,
|
|
733
795
|
lastResult: {
|
|
@@ -746,10 +808,8 @@ function writeSoftwareAgentContextSessionState(statePath, envelope, result, upda
|
|
|
746
808
|
}
|
|
747
809
|
function readSoftwareAgentContextSessionSummary(statePath) {
|
|
748
810
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const parsed = JSON.parse(readFileSync(statePath, "utf8"));
|
|
752
|
-
if (!parsed || parsed.schemaVersion !== 1 || !parsed.lastTaskId || !parsed.lastResult)
|
|
811
|
+
const parsed = readSoftwareAgentContextSessionState(statePath);
|
|
812
|
+
if (!parsed?.lastTaskId || !parsed.lastResult)
|
|
753
813
|
return undefined;
|
|
754
814
|
const result = parsed.lastResult;
|
|
755
815
|
const status = result.success === true ? "completed" : "failed";
|
|
@@ -771,6 +831,31 @@ function readSoftwareAgentContextSessionSummary(statePath) {
|
|
|
771
831
|
return undefined;
|
|
772
832
|
}
|
|
773
833
|
}
|
|
834
|
+
function readSoftwareAgentContextSessionState(statePath) {
|
|
835
|
+
try {
|
|
836
|
+
if (!existsSync(statePath))
|
|
837
|
+
return null;
|
|
838
|
+
const parsed = JSON.parse(readFileSync(statePath, "utf8"));
|
|
839
|
+
if (!isSoftwareAgentContextSessionState(parsed))
|
|
840
|
+
return null;
|
|
841
|
+
return parsed;
|
|
842
|
+
}
|
|
843
|
+
catch {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function isSoftwareAgentContextSessionState(value) {
|
|
848
|
+
return value
|
|
849
|
+
&& value.schemaVersion === 1
|
|
850
|
+
&& typeof value.sessionId === "string"
|
|
851
|
+
&& typeof value.updatedAt === "string"
|
|
852
|
+
&& (value.workMemoryDir === undefined || typeof value.workMemoryDir === "string")
|
|
853
|
+
&& typeof value.lastTaskId === "string"
|
|
854
|
+
&& value.lastResult
|
|
855
|
+
&& typeof value.lastResult.success === "boolean"
|
|
856
|
+
&& (typeof value.lastResult.exitCode === "number" || value.lastResult.exitCode === null)
|
|
857
|
+
&& typeof value.lastResult.durationMs === "number";
|
|
858
|
+
}
|
|
774
859
|
function readOptionalString(value, field) {
|
|
775
860
|
if (value === undefined || value === null)
|
|
776
861
|
return undefined;
|
|
@@ -933,16 +1018,10 @@ function compareSoftwareAgentTaskRecords(a, b) {
|
|
|
933
1018
|
return bTime - aTime;
|
|
934
1019
|
return b.taskId.localeCompare(a.taskId);
|
|
935
1020
|
}
|
|
936
|
-
function
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
"-C", opts.workdir,
|
|
943
|
-
];
|
|
944
|
-
if (opts.model)
|
|
945
|
-
args.push("-m", opts.model);
|
|
946
|
-
args.push("-");
|
|
947
|
-
return { cmd: opts.command, args };
|
|
1021
|
+
function compareSoftwareAgentContextSessions(a, b) {
|
|
1022
|
+
const bTime = Date.parse(b.updatedAt || "") || 0;
|
|
1023
|
+
const aTime = Date.parse(a.updatedAt || "") || 0;
|
|
1024
|
+
if (bTime !== aTime)
|
|
1025
|
+
return bTime - aTime;
|
|
1026
|
+
return b.sessionId.localeCompare(a.sessionId);
|
|
948
1027
|
}
|