akemon 0.3.5 → 0.3.6
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 +120 -0
- package/README.md +25 -0
- package/TRADEMARK.md +74 -0
- package/dist/cli.js +230 -39
- package/dist/server.js +96 -3
- package/dist/software-agent-peripheral.js +147 -9
- package/dist/software-agent-result-cli.js +69 -0
- package/dist/software-agent-stream-cli.js +23 -0
- package/dist/work-memory.js +295 -0
- package/package.json +5 -3
package/dist/server.js
CHANGED
|
@@ -163,6 +163,16 @@ async function readOwnerSoftwareAgentEnvelope(req, res, deps) {
|
|
|
163
163
|
envelope,
|
|
164
164
|
request: body,
|
|
165
165
|
});
|
|
166
|
+
if (readOptionalBooleanBody(body?.includeWorkMemoryContext, "includeWorkMemoryContext")) {
|
|
167
|
+
const workContext = await buildWorkMemoryContext({
|
|
168
|
+
workdir: deps.workdir,
|
|
169
|
+
agentName: deps.agentName,
|
|
170
|
+
purpose: `software-agent task: ${envelope.goal}`,
|
|
171
|
+
budget: readOptionalPositiveIntBody(body?.workMemoryContextBudget, "workMemoryContextBudget"),
|
|
172
|
+
});
|
|
173
|
+
envelope.workMemoryDir = workContext.workMemoryDir;
|
|
174
|
+
envelope.workMemoryContext = workContext.text;
|
|
175
|
+
}
|
|
166
176
|
return envelope;
|
|
167
177
|
}
|
|
168
178
|
catch (err) {
|
|
@@ -225,6 +235,9 @@ export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
|
225
235
|
type: "start",
|
|
226
236
|
taskId: event.taskId,
|
|
227
237
|
commandLine: event.commandLine,
|
|
238
|
+
contextSessionId: event.contextSessionId,
|
|
239
|
+
contextPacketPath: event.contextPacketPath,
|
|
240
|
+
workMemoryDir: event.workMemoryDir,
|
|
228
241
|
});
|
|
229
242
|
},
|
|
230
243
|
onStream(event) {
|
|
@@ -243,6 +256,9 @@ export async function handleSoftwareAgentRunStreamHttp(req, res, deps) {
|
|
|
243
256
|
exitCode: event.exitCode,
|
|
244
257
|
durationMs: event.durationMs,
|
|
245
258
|
result: event.result,
|
|
259
|
+
contextSessionId: event.contextSessionId,
|
|
260
|
+
contextPacketPath: event.contextPacketPath,
|
|
261
|
+
workMemoryDir: event.workMemoryDir,
|
|
246
262
|
});
|
|
247
263
|
},
|
|
248
264
|
},
|
|
@@ -282,7 +298,9 @@ export async function handleSoftwareAgentTasksHttp(req, res, deps) {
|
|
|
282
298
|
const taskLedgerDir = softwareAgentTaskLedgerDir(deps.workdir, deps.agentName);
|
|
283
299
|
if (url.pathname === basePath) {
|
|
284
300
|
const limit = readPositiveIntQuery(url.searchParams.get("limit"), 20, 100);
|
|
285
|
-
const tasks = listSoftwareAgentTaskRecords(taskLedgerDir, limit
|
|
301
|
+
const tasks = listSoftwareAgentTaskRecords(taskLedgerDir, limit, {
|
|
302
|
+
contextSessionId: url.searchParams.get("session") || undefined,
|
|
303
|
+
});
|
|
286
304
|
writeJsonResponse(res, 200, { tasks }, true);
|
|
287
305
|
return;
|
|
288
306
|
}
|
|
@@ -297,11 +315,57 @@ export async function handleSoftwareAgentTasksHttp(req, res, deps) {
|
|
|
297
315
|
writeJsonResponse(res, 404, { error: "Software-agent task not found" });
|
|
298
316
|
return;
|
|
299
317
|
}
|
|
300
|
-
|
|
318
|
+
let contextSession;
|
|
319
|
+
if (readBooleanQuery(url.searchParams.get("includeContext")) && task.contextSession?.sessionId) {
|
|
320
|
+
try {
|
|
321
|
+
contextSession = readSoftwareAgentContextSession(softwareAgentContextSessionDir(deps.workdir, deps.agentName), task.contextSession.sessionId, { includeContextPacket: true });
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
contextSession = null;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
writeJsonResponse(res, 200, { task, ...(contextSession ? { contextSession } : {}) }, true);
|
|
301
328
|
return;
|
|
302
329
|
}
|
|
303
330
|
writeJsonResponse(res, 404, { error: "Software-agent task endpoint not found" });
|
|
304
331
|
}
|
|
332
|
+
export async function handleSoftwareAgentContextSessionsHttp(req, res, deps) {
|
|
333
|
+
if (!requireOwnerRequest(req, res, deps.options))
|
|
334
|
+
return;
|
|
335
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
336
|
+
const basePath = "/self/software-agent/sessions";
|
|
337
|
+
const contextSessionDir = softwareAgentContextSessionDir(deps.workdir, deps.agentName);
|
|
338
|
+
if (url.pathname === basePath) {
|
|
339
|
+
const limit = readPositiveIntQuery(url.searchParams.get("limit"), 20, 100);
|
|
340
|
+
const sessions = listSoftwareAgentContextSessions(contextSessionDir, limit);
|
|
341
|
+
writeJsonResponse(res, 200, { sessions }, true);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (url.pathname.startsWith(`${basePath}/`)) {
|
|
345
|
+
const sessionId = decodeURIComponent(url.pathname.slice(basePath.length + 1));
|
|
346
|
+
if (!sessionId || sessionId.includes("/")) {
|
|
347
|
+
writeJsonResponse(res, 400, { error: "Invalid software-agent context session id" });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
let session;
|
|
351
|
+
try {
|
|
352
|
+
session = readSoftwareAgentContextSession(contextSessionDir, sessionId, {
|
|
353
|
+
includeContextPacket: readBooleanQuery(url.searchParams.get("includeContext")),
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
writeJsonResponse(res, 400, { error: err.message || "Invalid software-agent context session id" });
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!session) {
|
|
361
|
+
writeJsonResponse(res, 404, { error: "Software-agent context session not found" });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
writeJsonResponse(res, 200, { session }, true);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
writeJsonResponse(res, 404, { error: "Software-agent context session endpoint not found" });
|
|
368
|
+
}
|
|
305
369
|
function softwareAgentTaskLedgerDir(workdir, agentName) {
|
|
306
370
|
return join(workdir, ".akemon", "agents", agentName, "software-agent", "tasks");
|
|
307
371
|
}
|
|
@@ -316,6 +380,24 @@ function readPositiveIntQuery(value, fallback, max) {
|
|
|
316
380
|
return fallback;
|
|
317
381
|
return Math.min(parsed, max);
|
|
318
382
|
}
|
|
383
|
+
function readBooleanQuery(value) {
|
|
384
|
+
return value === "1" || value === "true" || value === "yes";
|
|
385
|
+
}
|
|
386
|
+
function readOptionalBooleanBody(value, field) {
|
|
387
|
+
if (value === undefined || value === null)
|
|
388
|
+
return false;
|
|
389
|
+
if (typeof value !== "boolean")
|
|
390
|
+
throw new Error(`Invalid ${field}: expected boolean`);
|
|
391
|
+
return value;
|
|
392
|
+
}
|
|
393
|
+
function readOptionalPositiveIntBody(value, field) {
|
|
394
|
+
if (value === undefined || value === null)
|
|
395
|
+
return undefined;
|
|
396
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
397
|
+
throw new Error(`Invalid ${field}: expected positive integer`);
|
|
398
|
+
}
|
|
399
|
+
return value;
|
|
400
|
+
}
|
|
319
401
|
function writeSoftwareAgentStreamEvent(res, event) {
|
|
320
402
|
if (res.destroyed)
|
|
321
403
|
return;
|
|
@@ -347,8 +429,9 @@ import { LongTermModule } from "./longterm-module.js";
|
|
|
347
429
|
import { ReflectionModule } from "./reflection-module.js";
|
|
348
430
|
import { ScriptModule } from "./script-module.js";
|
|
349
431
|
import { FileEventLog, PersistentEventBus } from "./event-bus.js";
|
|
350
|
-
import { CodexSoftwareAgentPeripheral, createOwnerTaskEnvelope, listSoftwareAgentTaskRecords, readSoftwareAgentTaskRecord, } from "./software-agent-peripheral.js";
|
|
432
|
+
import { CodexSoftwareAgentPeripheral, createOwnerTaskEnvelope, listSoftwareAgentContextSessions, listSoftwareAgentTaskRecords, readSoftwareAgentContextSession, readSoftwareAgentTaskRecord, } from "./software-agent-peripheral.js";
|
|
351
433
|
import { buildSoftwareAgentMemorySummary } from "./software-agent-memory.js";
|
|
434
|
+
import { buildWorkMemoryContext, workMemoryDir } from "./work-memory.js";
|
|
352
435
|
import { SIG, sig } from "./types.js";
|
|
353
436
|
import { loadConversation, listConversations, buildLLMContext } from "./context.js";
|
|
354
437
|
import { redactSecrets } from "./redaction.js";
|
|
@@ -458,6 +541,15 @@ export async function serve(options) {
|
|
|
458
541
|
return;
|
|
459
542
|
}
|
|
460
543
|
const requestPath = req.url?.split("?")[0] || "";
|
|
544
|
+
if (req.method === "GET"
|
|
545
|
+
&& (requestPath === "/self/software-agent/sessions" || requestPath.startsWith("/self/software-agent/sessions/"))) {
|
|
546
|
+
await handleSoftwareAgentContextSessionsHttp(req, res, {
|
|
547
|
+
options,
|
|
548
|
+
workdir,
|
|
549
|
+
agentName: options.agentName,
|
|
550
|
+
});
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
461
553
|
if (req.method === "GET"
|
|
462
554
|
&& (requestPath === "/self/software-agent/tasks" || requestPath.startsWith("/self/software-agent/tasks/"))) {
|
|
463
555
|
await handleSoftwareAgentTasksHttp(req, res, {
|
|
@@ -664,6 +756,7 @@ export async function serve(options) {
|
|
|
664
756
|
sandbox: "workspace-write",
|
|
665
757
|
taskLedgerDir: softwareAgentTaskLedgerDir(workdir, options.agentName),
|
|
666
758
|
contextSessionDir: softwareAgentContextSessionDir(workdir, options.agentName),
|
|
759
|
+
workMemoryDir: workMemoryDir(workdir, options.agentName),
|
|
667
760
|
envPolicy: options.softwareAgentEnvPolicy,
|
|
668
761
|
envAllowlist: options.softwareAgentEnvAllowlist,
|
|
669
762
|
});
|
|
@@ -133,6 +133,7 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
133
133
|
workdirStatus: this.collectWorkdirStatus(currentWorkdir),
|
|
134
134
|
taskLedgerDir: this.config.taskLedgerDir,
|
|
135
135
|
contextSessionDir: this.config.contextSessionDir,
|
|
136
|
+
workMemoryDir: this.config.workMemoryDir,
|
|
136
137
|
environment: buildSoftwareAgentChildEnvironment({
|
|
137
138
|
policy: this.config.envPolicy,
|
|
138
139
|
allowlist: this.config.envAllowlist,
|
|
@@ -162,7 +163,14 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
162
163
|
const contextSessionId = normalizeContextSessionId(envelope.contextSessionId) || taskId;
|
|
163
164
|
const workdirSafety = resolveWorkdirSafety(this.config.workdir, envelope.workdir || this.config.workdir, envelope.workdirSafety?.allowOutsideWorkdir || false);
|
|
164
165
|
const workdir = workdirSafety.effectiveWorkdir;
|
|
165
|
-
let effectiveEnvelope = {
|
|
166
|
+
let effectiveEnvelope = {
|
|
167
|
+
...envelope,
|
|
168
|
+
taskId,
|
|
169
|
+
contextSessionId,
|
|
170
|
+
workdir,
|
|
171
|
+
workdirSafety,
|
|
172
|
+
workMemoryDir: envelope.workMemoryDir || this.config.workMemoryDir,
|
|
173
|
+
};
|
|
166
174
|
const contextSession = this.config.contextSessionDir
|
|
167
175
|
? writeSoftwareAgentContextPacket(this.config.contextSessionDir, effectiveEnvelope)
|
|
168
176
|
: undefined;
|
|
@@ -184,6 +192,11 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
184
192
|
const spawnImpl = this.config.spawnImpl || spawn;
|
|
185
193
|
const timeoutMs = envelope.timeoutMs || this.config.defaultTimeoutMs || DEFAULT_TIMEOUT_MS;
|
|
186
194
|
const workdirStatus = this.collectWorkdirStatus(workdir);
|
|
195
|
+
const taskMetadata = {
|
|
196
|
+
contextSessionId: effectiveEnvelope.contextSessionId,
|
|
197
|
+
contextPacketPath: effectiveEnvelope.contextPacketPath,
|
|
198
|
+
workMemoryDir: effectiveEnvelope.workMemoryDir,
|
|
199
|
+
};
|
|
187
200
|
const childEnvironment = buildSoftwareAgentChildEnvironment({
|
|
188
201
|
policy: this.config.envPolicy,
|
|
189
202
|
allowlist: this.config.envAllowlist,
|
|
@@ -212,7 +225,7 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
212
225
|
});
|
|
213
226
|
const origin = "software_agent";
|
|
214
227
|
relay.sendTaskStart(taskId, origin, commandLine);
|
|
215
|
-
observer?.onStart?.({ taskId, origin, commandLine });
|
|
228
|
+
observer?.onStart?.({ taskId, origin, commandLine, ...taskMetadata });
|
|
216
229
|
this.bus?.emit(SIG.TASK_STARTED, sig(SIG.TASK_STARTED, {
|
|
217
230
|
taskId,
|
|
218
231
|
taskType: "software_agent",
|
|
@@ -241,9 +254,16 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
241
254
|
error: err.message || String(err),
|
|
242
255
|
exitCode: null,
|
|
243
256
|
durationMs,
|
|
257
|
+
...taskMetadata,
|
|
244
258
|
};
|
|
245
259
|
relay.sendTaskEnd(taskId, null, durationMs);
|
|
246
|
-
observer?.onEnd?.({
|
|
260
|
+
observer?.onEnd?.({
|
|
261
|
+
taskId,
|
|
262
|
+
exitCode: null,
|
|
263
|
+
durationMs,
|
|
264
|
+
result: redactSecrets(result),
|
|
265
|
+
...taskMetadata,
|
|
266
|
+
});
|
|
247
267
|
const completedAt = new Date().toISOString();
|
|
248
268
|
this.writeTaskRecord({
|
|
249
269
|
...baseTaskRecord(),
|
|
@@ -308,9 +328,16 @@ export class CodexSoftwareAgentPeripheral {
|
|
|
308
328
|
error: success ? undefined : error || stderr.trim() || `codex exited with code ${exitCode}`,
|
|
309
329
|
exitCode,
|
|
310
330
|
durationMs,
|
|
331
|
+
...taskMetadata,
|
|
311
332
|
};
|
|
312
333
|
relay.sendTaskEnd(taskId, exitCode, durationMs);
|
|
313
|
-
observer?.onEnd?.({
|
|
334
|
+
observer?.onEnd?.({
|
|
335
|
+
taskId,
|
|
336
|
+
exitCode,
|
|
337
|
+
durationMs,
|
|
338
|
+
result: redactSecrets(result),
|
|
339
|
+
...taskMetadata,
|
|
340
|
+
});
|
|
314
341
|
const completedAt = new Date().toISOString();
|
|
315
342
|
this.writeTaskRecord({
|
|
316
343
|
...baseTaskRecord(),
|
|
@@ -451,6 +478,9 @@ export function buildTaskEnvelopePrompt(envelope) {
|
|
|
451
478
|
`Risk level: ${envelope.riskLevel}`,
|
|
452
479
|
`Workdir: ${envelope.workdir}`,
|
|
453
480
|
];
|
|
481
|
+
if (envelope.workMemoryDir) {
|
|
482
|
+
lines.push(`Work memory directory: ${envelope.workMemoryDir}`);
|
|
483
|
+
}
|
|
454
484
|
if (envelope.contextPacketPath) {
|
|
455
485
|
lines.push(`Context packet path: ${envelope.contextPacketPath}`);
|
|
456
486
|
}
|
|
@@ -472,6 +502,20 @@ export function buildTaskEnvelopePrompt(envelope) {
|
|
|
472
502
|
lines.push(envelope.memorySummary.trim());
|
|
473
503
|
lines.push("");
|
|
474
504
|
}
|
|
505
|
+
if (envelope.workMemoryDir) {
|
|
506
|
+
lines.push("Work memory:");
|
|
507
|
+
lines.push("- This is user-owned working context for engineering/task continuity.");
|
|
508
|
+
lines.push("- You may read it with grep, direct file browsing, or semantic review as appropriate.");
|
|
509
|
+
lines.push("- You may update files under this directory when the task or user asks you to maintain work memory.");
|
|
510
|
+
lines.push("- Do not read or edit Akemon self memory as part of this software-agent task.");
|
|
511
|
+
lines.push("- For a quick append, use `akemon work-note \"<durable work memory>\" --source codex --kind note`.");
|
|
512
|
+
lines.push("");
|
|
513
|
+
}
|
|
514
|
+
if (envelope.workMemoryContext?.trim()) {
|
|
515
|
+
lines.push("Included work-memory context:");
|
|
516
|
+
lines.push(envelope.workMemoryContext.trim());
|
|
517
|
+
lines.push("");
|
|
518
|
+
}
|
|
475
519
|
if (envelope.allowedActions?.length) {
|
|
476
520
|
lines.push("Allowed actions:");
|
|
477
521
|
for (const item of envelope.allowedActions)
|
|
@@ -492,7 +536,9 @@ export function buildTaskEnvelopePrompt(envelope) {
|
|
|
492
536
|
lines.push("Instructions:");
|
|
493
537
|
lines.push("- Treat this envelope as the complete Akemon-provided context for this task.");
|
|
494
538
|
lines.push("- Do not attempt to read Akemon private memory outside the visible context above.");
|
|
539
|
+
lines.push("- Do not read or edit Akemon self memory unless the user explicitly names a normal file to inspect.");
|
|
495
540
|
lines.push("- Work only in the stated workdir unless the envelope explicitly allows otherwise.");
|
|
541
|
+
lines.push("- If you learn durable work memory, update the work memory directory or use `akemon work-note`.");
|
|
496
542
|
lines.push("- Report what changed, what you verified, and any remaining risk.");
|
|
497
543
|
return lines.join("\n");
|
|
498
544
|
}
|
|
@@ -504,6 +550,7 @@ export function buildContextPacketLaunchPrompt(envelope, contextPacketPath) {
|
|
|
504
550
|
`Akemon context session: ${envelope.contextSessionId || "(one-shot)"}`,
|
|
505
551
|
`Context packet: ${contextPacketPath}`,
|
|
506
552
|
`Workdir: ${envelope.workdir}`,
|
|
553
|
+
`Work memory directory: ${envelope.workMemoryDir || "(not configured)"}`,
|
|
507
554
|
"",
|
|
508
555
|
"Goal:",
|
|
509
556
|
envelope.goal,
|
|
@@ -512,7 +559,9 @@ export function buildContextPacketLaunchPrompt(envelope, contextPacketPath) {
|
|
|
512
559
|
"- Read the context packet first before doing repository work.",
|
|
513
560
|
"- Treat that file as the complete Akemon-provided context for this task.",
|
|
514
561
|
"- Do not read Akemon private memory outside the context packet.",
|
|
562
|
+
"- Do not read or edit Akemon self memory unless the user explicitly names a normal file to inspect.",
|
|
515
563
|
"- Work only in the stated workdir unless the packet explicitly allows otherwise.",
|
|
564
|
+
"- If you learn durable work memory, update the work memory directory or use `akemon work-note`.",
|
|
516
565
|
"- Report what changed, what you verified, and any remaining risk.",
|
|
517
566
|
].join("\n");
|
|
518
567
|
}
|
|
@@ -618,8 +667,9 @@ export function readGitWorktreeStatus(workdir) {
|
|
|
618
667
|
};
|
|
619
668
|
}
|
|
620
669
|
}
|
|
621
|
-
export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20) {
|
|
670
|
+
export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20, opts = {}) {
|
|
622
671
|
const safeLimit = normalizeTaskRecordLimit(limit);
|
|
672
|
+
const contextSessionId = opts.contextSessionId?.trim();
|
|
623
673
|
try {
|
|
624
674
|
if (!existsSync(taskLedgerDir))
|
|
625
675
|
return [];
|
|
@@ -627,6 +677,7 @@ export function listSoftwareAgentTaskRecords(taskLedgerDir, limit = 20) {
|
|
|
627
677
|
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
628
678
|
.map((entry) => readSoftwareAgentTaskRecordFile(join(taskLedgerDir, entry.name)))
|
|
629
679
|
.filter((record) => !!record)
|
|
680
|
+
.filter((record) => !contextSessionId || record.contextSession?.sessionId === contextSessionId || record.envelope.contextSessionId === contextSessionId)
|
|
630
681
|
.sort(compareSoftwareAgentTaskRecords)
|
|
631
682
|
.slice(0, safeLimit);
|
|
632
683
|
}
|
|
@@ -638,6 +689,62 @@ export function readSoftwareAgentTaskRecord(taskLedgerDir, taskId) {
|
|
|
638
689
|
const file = join(taskLedgerDir, `${safeTaskFilename(taskId)}.json`);
|
|
639
690
|
return readSoftwareAgentTaskRecordFile(file);
|
|
640
691
|
}
|
|
692
|
+
export function listSoftwareAgentContextSessions(contextSessionDir, limit = 20) {
|
|
693
|
+
const safeLimit = normalizeTaskRecordLimit(limit);
|
|
694
|
+
try {
|
|
695
|
+
if (!existsSync(contextSessionDir))
|
|
696
|
+
return [];
|
|
697
|
+
return readdirSync(contextSessionDir, { withFileTypes: true })
|
|
698
|
+
.filter((entry) => entry.isDirectory())
|
|
699
|
+
.map((entry) => {
|
|
700
|
+
try {
|
|
701
|
+
return readSoftwareAgentContextSession(contextSessionDir, entry.name);
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
})
|
|
707
|
+
.filter((record) => !!record)
|
|
708
|
+
.sort(compareSoftwareAgentContextSessions)
|
|
709
|
+
.slice(0, safeLimit);
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
return [];
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
export function readSoftwareAgentContextSession(contextSessionDir, sessionId, opts = {}) {
|
|
716
|
+
const safeSessionId = normalizeContextSessionId(sessionId, "sessionId");
|
|
717
|
+
if (!safeSessionId)
|
|
718
|
+
return null;
|
|
719
|
+
const sessionDir = join(contextSessionDir, safeSessionId);
|
|
720
|
+
if (!existsSync(sessionDir))
|
|
721
|
+
return null;
|
|
722
|
+
const packetPath = join(sessionDir, CONTEXT_PACKET_FILENAME);
|
|
723
|
+
const statePath = join(sessionDir, CONTEXT_SESSION_STATE_FILENAME);
|
|
724
|
+
const state = readSoftwareAgentContextSessionState(statePath);
|
|
725
|
+
const record = {
|
|
726
|
+
sessionId: safeSessionId,
|
|
727
|
+
packetPath,
|
|
728
|
+
statePath,
|
|
729
|
+
hasContextPacket: existsSync(packetPath),
|
|
730
|
+
};
|
|
731
|
+
if (state) {
|
|
732
|
+
record.updatedAt = state.updatedAt;
|
|
733
|
+
record.workMemoryDir = state.workMemoryDir;
|
|
734
|
+
record.lastTaskId = state.lastTaskId;
|
|
735
|
+
record.lastGoal = state.lastGoal;
|
|
736
|
+
record.lastResult = state.lastResult;
|
|
737
|
+
}
|
|
738
|
+
if (opts.includeContextPacket && record.hasContextPacket) {
|
|
739
|
+
try {
|
|
740
|
+
record.contextPacket = readFileSync(packetPath, "utf8");
|
|
741
|
+
}
|
|
742
|
+
catch {
|
|
743
|
+
record.contextPacket = "";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return record;
|
|
747
|
+
}
|
|
641
748
|
export function pruneSoftwareAgentTaskRecords(taskLedgerDir, maxRecords = DEFAULT_TASK_LEDGER_MAX_RECORDS, preserveTaskId) {
|
|
642
749
|
const safeMaxRecords = normalizeTaskLedgerMaxRecords(maxRecords);
|
|
643
750
|
try {
|
|
@@ -728,6 +835,7 @@ function writeSoftwareAgentContextSessionState(statePath, envelope, result, upda
|
|
|
728
835
|
schemaVersion: 1,
|
|
729
836
|
sessionId: envelope.contextSessionId,
|
|
730
837
|
updatedAt,
|
|
838
|
+
workMemoryDir: envelope.workMemoryDir,
|
|
731
839
|
lastTaskId: result.taskId,
|
|
732
840
|
lastGoal: envelope.goal,
|
|
733
841
|
lastResult: {
|
|
@@ -746,10 +854,8 @@ function writeSoftwareAgentContextSessionState(statePath, envelope, result, upda
|
|
|
746
854
|
}
|
|
747
855
|
function readSoftwareAgentContextSessionSummary(statePath) {
|
|
748
856
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
const parsed = JSON.parse(readFileSync(statePath, "utf8"));
|
|
752
|
-
if (!parsed || parsed.schemaVersion !== 1 || !parsed.lastTaskId || !parsed.lastResult)
|
|
857
|
+
const parsed = readSoftwareAgentContextSessionState(statePath);
|
|
858
|
+
if (!parsed?.lastTaskId || !parsed.lastResult)
|
|
753
859
|
return undefined;
|
|
754
860
|
const result = parsed.lastResult;
|
|
755
861
|
const status = result.success === true ? "completed" : "failed";
|
|
@@ -771,6 +877,31 @@ function readSoftwareAgentContextSessionSummary(statePath) {
|
|
|
771
877
|
return undefined;
|
|
772
878
|
}
|
|
773
879
|
}
|
|
880
|
+
function readSoftwareAgentContextSessionState(statePath) {
|
|
881
|
+
try {
|
|
882
|
+
if (!existsSync(statePath))
|
|
883
|
+
return null;
|
|
884
|
+
const parsed = JSON.parse(readFileSync(statePath, "utf8"));
|
|
885
|
+
if (!isSoftwareAgentContextSessionState(parsed))
|
|
886
|
+
return null;
|
|
887
|
+
return parsed;
|
|
888
|
+
}
|
|
889
|
+
catch {
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function isSoftwareAgentContextSessionState(value) {
|
|
894
|
+
return value
|
|
895
|
+
&& value.schemaVersion === 1
|
|
896
|
+
&& typeof value.sessionId === "string"
|
|
897
|
+
&& typeof value.updatedAt === "string"
|
|
898
|
+
&& (value.workMemoryDir === undefined || typeof value.workMemoryDir === "string")
|
|
899
|
+
&& typeof value.lastTaskId === "string"
|
|
900
|
+
&& value.lastResult
|
|
901
|
+
&& typeof value.lastResult.success === "boolean"
|
|
902
|
+
&& (typeof value.lastResult.exitCode === "number" || value.lastResult.exitCode === null)
|
|
903
|
+
&& typeof value.lastResult.durationMs === "number";
|
|
904
|
+
}
|
|
774
905
|
function readOptionalString(value, field) {
|
|
775
906
|
if (value === undefined || value === null)
|
|
776
907
|
return undefined;
|
|
@@ -933,6 +1064,13 @@ function compareSoftwareAgentTaskRecords(a, b) {
|
|
|
933
1064
|
return bTime - aTime;
|
|
934
1065
|
return b.taskId.localeCompare(a.taskId);
|
|
935
1066
|
}
|
|
1067
|
+
function compareSoftwareAgentContextSessions(a, b) {
|
|
1068
|
+
const bTime = Date.parse(b.updatedAt || "") || 0;
|
|
1069
|
+
const aTime = Date.parse(a.updatedAt || "") || 0;
|
|
1070
|
+
if (bTime !== aTime)
|
|
1071
|
+
return bTime - aTime;
|
|
1072
|
+
return b.sessionId.localeCompare(a.sessionId);
|
|
1073
|
+
}
|
|
936
1074
|
function buildCodexExecCommand(opts) {
|
|
937
1075
|
const args = [
|
|
938
1076
|
"exec",
|
|
@@ -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");
|