akemon 0.2.22 → 0.2.24
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/dist/engine-peripheral.js +37 -33
- package/dist/engine-queue.js +95 -0
- package/dist/reflection-module.js +1 -1
- package/dist/server.js +45 -39
- package/dist/task-module.js +11 -7
- package/package.json +1 -1
|
@@ -24,9 +24,6 @@ export class EnginePeripheral {
|
|
|
24
24
|
tags = ["engine", "llm"];
|
|
25
25
|
config;
|
|
26
26
|
bus = null;
|
|
27
|
-
/** Engine mutual exclusion — only one engine process at a time */
|
|
28
|
-
busy = false;
|
|
29
|
-
busySince = 0;
|
|
30
27
|
/** Last execution trace (for error reporting) */
|
|
31
28
|
lastTrace = [];
|
|
32
29
|
constructor(config) {
|
|
@@ -70,38 +67,15 @@ export class EnginePeripheral {
|
|
|
70
67
|
}, this.id);
|
|
71
68
|
}
|
|
72
69
|
// ---------------------------------------------------------------------------
|
|
73
|
-
// Engine mutex helpers
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
acquire() {
|
|
76
|
-
if (this.busy)
|
|
77
|
-
return false;
|
|
78
|
-
this.busy = true;
|
|
79
|
-
this.busySince = Date.now();
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
release() {
|
|
83
|
-
this.busy = false;
|
|
84
|
-
this.busySince = 0;
|
|
85
|
-
}
|
|
86
|
-
/** Watchdog: reset if stuck for > 10 min */
|
|
87
|
-
checkStuck() {
|
|
88
|
-
if (this.busy && this.busySince > 0 && Date.now() - this.busySince > 10 * 60 * 1000) {
|
|
89
|
-
console.log(`[watchdog] engine stuck for ${Math.round((Date.now() - this.busySince) / 1000)}s, force-resetting`);
|
|
90
|
-
this.release();
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
// ---------------------------------------------------------------------------
|
|
96
70
|
// Unified engine runner
|
|
97
71
|
// ---------------------------------------------------------------------------
|
|
98
|
-
async runEngine(task, allowAll, extraAllowedTools) {
|
|
72
|
+
async runEngine(task, allowAll, extraAllowedTools, signal) {
|
|
99
73
|
const { engine, model, workdir } = this.config;
|
|
100
74
|
if (engine === "raw") {
|
|
101
75
|
return this.runRawEngine(task);
|
|
102
76
|
}
|
|
103
77
|
const cmd = buildEngineCommand(engine, model, allowAll ?? this.config.allowAll, extraAllowedTools);
|
|
104
|
-
return runCommand(cmd.cmd, cmd.args, task, workdir, cmd.stdinMode);
|
|
78
|
+
return runCommand(cmd.cmd, cmd.args, task, workdir, cmd.stdinMode, signal);
|
|
105
79
|
}
|
|
106
80
|
// ---------------------------------------------------------------------------
|
|
107
81
|
// Raw engine: OpenAI-compatible API with tool call loop
|
|
@@ -390,7 +364,7 @@ function buildEngineCommand(engine, model, allowAll, extraAllowedTools) {
|
|
|
390
364
|
return { cmd: engine, args: [], stdinMode: true };
|
|
391
365
|
}
|
|
392
366
|
}
|
|
393
|
-
function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
367
|
+
function runCommand(cmd, args, task, cwd, stdinMode = true, signal) {
|
|
394
368
|
return new Promise((resolve, reject) => {
|
|
395
369
|
const { CLAUDECODE, ...cleanEnv } = process.env;
|
|
396
370
|
const finalArgs = stdinMode ? args : [...args, task];
|
|
@@ -399,8 +373,30 @@ function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
|
399
373
|
cwd,
|
|
400
374
|
env: cleanEnv,
|
|
401
375
|
stdio: [stdinMode ? "pipe" : "ignore", "pipe", "pipe"],
|
|
402
|
-
timeout: 300_000,
|
|
403
376
|
});
|
|
377
|
+
// Abort → SIGTERM, then SIGKILL after a grace period so a hung engine can't
|
|
378
|
+
// hold the slot past the caller's deadline.
|
|
379
|
+
let aborted = false;
|
|
380
|
+
const onAbort = () => {
|
|
381
|
+
if (aborted)
|
|
382
|
+
return;
|
|
383
|
+
aborted = true;
|
|
384
|
+
console.log(`[${cmd}] aborted, killing pid=${child.pid}`);
|
|
385
|
+
try {
|
|
386
|
+
child.kill("SIGTERM");
|
|
387
|
+
}
|
|
388
|
+
catch { }
|
|
389
|
+
setTimeout(() => { try {
|
|
390
|
+
child.kill("SIGKILL");
|
|
391
|
+
}
|
|
392
|
+
catch { } }, 3000).unref();
|
|
393
|
+
};
|
|
394
|
+
if (signal) {
|
|
395
|
+
if (signal.aborted)
|
|
396
|
+
onAbort();
|
|
397
|
+
else
|
|
398
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
399
|
+
}
|
|
404
400
|
if (stdinMode && child.stdin) {
|
|
405
401
|
child.stdin.on("error", () => { });
|
|
406
402
|
child.stdin.write(task);
|
|
@@ -414,12 +410,17 @@ function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
|
414
410
|
child.stderr?.on("data", (chunk) => {
|
|
415
411
|
stderr += chunk.toString();
|
|
416
412
|
});
|
|
417
|
-
child.on("close", (code) => {
|
|
418
|
-
|
|
413
|
+
child.on("close", (code, killSignal) => {
|
|
414
|
+
signal?.removeEventListener("abort", onAbort);
|
|
415
|
+
console.log(`[${cmd}] exit=${code}${killSignal ? ` signal=${killSignal}` : ""} stdout=${stdout.length}b stderr=${stderr.length}b`);
|
|
419
416
|
if (stderr)
|
|
420
417
|
console.log(`[${cmd}] stderr:\n${stderr}`);
|
|
421
418
|
if (stdout)
|
|
422
419
|
console.log(`[${cmd}] stdout:\n${stdout}`);
|
|
420
|
+
if (aborted) {
|
|
421
|
+
reject(new Error(`${cmd} aborted`));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
423
424
|
const output = stdout.trim();
|
|
424
425
|
if (output) {
|
|
425
426
|
resolve(output);
|
|
@@ -428,6 +429,9 @@ function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
|
428
429
|
reject(new Error(`${cmd} exited with code ${code}, no stdout`));
|
|
429
430
|
}
|
|
430
431
|
});
|
|
431
|
-
child.on("error",
|
|
432
|
+
child.on("error", (err) => {
|
|
433
|
+
signal?.removeEventListener("abort", onAbort);
|
|
434
|
+
reject(err);
|
|
435
|
+
});
|
|
432
436
|
});
|
|
433
437
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EngineQueue — priority-aware single-slot scheduler for the local engine.
|
|
3
|
+
*
|
|
4
|
+
* Only one engine subprocess may run at a time. All modules (orders, digestion,
|
|
5
|
+
* reflection, script activities, MCP ad-hoc calls, …) go through this queue so
|
|
6
|
+
* that higher-priority work does not starve behind background busywork.
|
|
7
|
+
*
|
|
8
|
+
* Ordering:
|
|
9
|
+
* - When the slot is free, acquire() returns immediately.
|
|
10
|
+
* - Otherwise callers wait; when the running job releases the slot, the
|
|
11
|
+
* highest-priority waiter wins (FIFO within the same priority).
|
|
12
|
+
* - If the caller's deadline elapses first, acquire() rejects with
|
|
13
|
+
* "Engine busy timeout" and the caller is removed from the queue.
|
|
14
|
+
*
|
|
15
|
+
* Priority semantics (keep in sync with types.ts ComputeRequest.priority):
|
|
16
|
+
* - high — user-waiting work (orders, user tasks, direct MCP calls)
|
|
17
|
+
* - normal — periodic self-maintenance that must not starve (digestion,
|
|
18
|
+
* reflection)
|
|
19
|
+
* - low — background enrichment (platform tasks, script activities,
|
|
20
|
+
* long-term, identity compression)
|
|
21
|
+
*/
|
|
22
|
+
const PRIORITY_RANK = { high: 3, normal: 2, low: 1 };
|
|
23
|
+
export class EngineQueue {
|
|
24
|
+
busy = false;
|
|
25
|
+
busySince = 0;
|
|
26
|
+
waiters = [];
|
|
27
|
+
/** Wait up to `deadlineMs` for the slot, then take it. */
|
|
28
|
+
acquire(priority, deadlineMs) {
|
|
29
|
+
if (!this.busy) {
|
|
30
|
+
this.busy = true;
|
|
31
|
+
this.busySince = Date.now();
|
|
32
|
+
return Promise.resolve();
|
|
33
|
+
}
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const waiter = {
|
|
36
|
+
priority,
|
|
37
|
+
enqueuedAt: Date.now(),
|
|
38
|
+
resolve,
|
|
39
|
+
reject,
|
|
40
|
+
timer: setTimeout(() => {
|
|
41
|
+
const idx = this.waiters.indexOf(waiter);
|
|
42
|
+
if (idx >= 0)
|
|
43
|
+
this.waiters.splice(idx, 1);
|
|
44
|
+
reject(new Error(`Engine busy timeout (${Math.round(deadlineMs / 60000)} min)`));
|
|
45
|
+
}, deadlineMs),
|
|
46
|
+
};
|
|
47
|
+
this.waiters.push(waiter);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Release the slot and hand it to the best waiter, if any. */
|
|
51
|
+
release() {
|
|
52
|
+
const next = this.pickNext();
|
|
53
|
+
if (!next) {
|
|
54
|
+
this.busy = false;
|
|
55
|
+
this.busySince = 0;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.waiters.splice(this.waiters.indexOf(next), 1);
|
|
59
|
+
clearTimeout(next.timer);
|
|
60
|
+
this.busySince = Date.now();
|
|
61
|
+
next.resolve();
|
|
62
|
+
}
|
|
63
|
+
/** Take the slot synchronously (used by MCP fast-path when !isBusy). */
|
|
64
|
+
tryAcquire() {
|
|
65
|
+
if (this.busy)
|
|
66
|
+
return false;
|
|
67
|
+
this.busy = true;
|
|
68
|
+
this.busySince = Date.now();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
isBusy() {
|
|
72
|
+
return this.busy;
|
|
73
|
+
}
|
|
74
|
+
queueDepth() {
|
|
75
|
+
return this.waiters.length;
|
|
76
|
+
}
|
|
77
|
+
/** How long has the current holder held the slot, in ms. 0 if free. */
|
|
78
|
+
heldMs() {
|
|
79
|
+
return this.busy && this.busySince ? Date.now() - this.busySince : 0;
|
|
80
|
+
}
|
|
81
|
+
pickNext() {
|
|
82
|
+
if (this.waiters.length === 0)
|
|
83
|
+
return null;
|
|
84
|
+
let best = this.waiters[0];
|
|
85
|
+
for (let i = 1; i < this.waiters.length; i++) {
|
|
86
|
+
const w = this.waiters[i];
|
|
87
|
+
const wr = PRIORITY_RANK[w.priority];
|
|
88
|
+
const br = PRIORITY_RANK[best.priority];
|
|
89
|
+
if (wr > br || (wr === br && w.enqueuedAt < best.enqueuedAt)) {
|
|
90
|
+
best = w;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return best;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -183,7 +183,7 @@ ${discText}`,
|
|
|
183
183
|
- Lower confidence on disproven beliefs
|
|
184
184
|
|
|
185
185
|
Reply ONLY JSON: {"discoveries":[{"capability":"skill or lesson","confidence":0.0-1.0,"evidence":"what supports this"}]}`,
|
|
186
|
-
priority: "
|
|
186
|
+
priority: "normal",
|
|
187
187
|
});
|
|
188
188
|
if (result.success && result.response) {
|
|
189
189
|
const parsed = extractJson(result.response);
|
package/dist/server.js
CHANGED
|
@@ -7,10 +7,15 @@ import { initWorld, initBioState, initGuide, getSelfState, loadRecentCanvasEntri
|
|
|
7
7
|
// V2: module-level instances (set in serve())
|
|
8
8
|
let _engineP = null;
|
|
9
9
|
let _bus = null;
|
|
10
|
-
// Engine mutual exclusion —
|
|
11
|
-
|
|
12
|
-
let engineBusySince = 0;
|
|
10
|
+
// Engine mutual exclusion — priority queue so modules can't starve each other
|
|
11
|
+
const engineQueue = new EngineQueue();
|
|
13
12
|
let lastEngineTrace = [];
|
|
13
|
+
// How long a caller will wait for the engine slot before giving up.
|
|
14
|
+
const ENGINE_WAIT_DEADLINE_MS = 5 * 60 * 1000;
|
|
15
|
+
// Hard cap on a single engine run. Beyond this the subprocess is aborted so
|
|
16
|
+
// the slot is returned to the queue. Shorter than the old 8 min — opencode
|
|
17
|
+
// runs that take longer almost always hang forever.
|
|
18
|
+
const ENGINE_EXEC_TIMEOUT_MS = 3 * 60 * 1000;
|
|
14
19
|
// ---------------------------------------------------------------------------
|
|
15
20
|
// V2 Event helpers — emit signals to EventBus
|
|
16
21
|
// ---------------------------------------------------------------------------
|
|
@@ -74,6 +79,7 @@ function promptOwner(task, isHuman) {
|
|
|
74
79
|
}
|
|
75
80
|
import { RelayPeripheral } from "./relay-peripheral.js";
|
|
76
81
|
import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
|
|
82
|
+
import { EngineQueue } from "./engine-queue.js";
|
|
77
83
|
import { BioStateModule } from "./bio-module.js";
|
|
78
84
|
import { MemoryModule } from "./memory-module.js";
|
|
79
85
|
import { RoleModule } from "./role-module.js";
|
|
@@ -92,11 +98,11 @@ const LLM_ENGINES = LLM_ENGINES_SET;
|
|
|
92
98
|
// Engine execution — delegates to EnginePeripheral (V2 Step 3)
|
|
93
99
|
// ---------------------------------------------------------------------------
|
|
94
100
|
/** Unified engine runner — delegates to EnginePeripheral */
|
|
95
|
-
function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay) {
|
|
101
|
+
function runEngine(engine, model, allowAll, task, workdir, extraAllowedTools, relay, signal) {
|
|
96
102
|
if (!_engineP) {
|
|
97
103
|
throw new Error("Engine peripheral not initialized");
|
|
98
104
|
}
|
|
99
|
-
const result = _engineP.runEngine(task, allowAll, extraAllowedTools);
|
|
105
|
+
const result = _engineP.runEngine(task, allowAll, extraAllowedTools, signal);
|
|
100
106
|
// Sync trace back to module-level for reporting
|
|
101
107
|
result.then(() => { lastEngineTrace = _engineP.lastTrace; }).catch(() => { lastEngineTrace = _engineP.lastTrace; });
|
|
102
108
|
return result;
|
|
@@ -146,8 +152,13 @@ export async function serve(options) {
|
|
|
146
152
|
promptOwner,
|
|
147
153
|
runCollaborativeQuery: (task, selfName, relayHttp, engine, model, allowAll, workdir, relay) => runCollaborativeQuery(task, selfName, relayHttp, engine, model, allowAll, workdir, runEngine, relay),
|
|
148
154
|
autoRoute,
|
|
149
|
-
isEngineBusy: () =>
|
|
150
|
-
setEngineBusy: (busy) => {
|
|
155
|
+
isEngineBusy: () => engineQueue.isBusy(),
|
|
156
|
+
setEngineBusy: (busy) => {
|
|
157
|
+
if (busy)
|
|
158
|
+
engineQueue.tryAcquire();
|
|
159
|
+
else
|
|
160
|
+
engineQueue.release();
|
|
161
|
+
},
|
|
151
162
|
emitTaskCompleted,
|
|
152
163
|
};
|
|
153
164
|
const httpServer = createServer(async (req, res) => {
|
|
@@ -338,45 +349,35 @@ export async function serve(options) {
|
|
|
338
349
|
const bus = new SimpleEventBus();
|
|
339
350
|
// Peripheral registry — Core routes by capability
|
|
340
351
|
const peripherals = [relay, engineP];
|
|
341
|
-
// requestCompute:
|
|
352
|
+
// requestCompute: acquire the engine slot (priority-aware), execute with a
|
|
353
|
+
// hard timeout, and release. The slot release and subprocess kill are both
|
|
354
|
+
// driven by the same AbortController so a stuck engine can't hold the lock.
|
|
342
355
|
async function requestCompute(req) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
while (engineBusy) {
|
|
346
|
-
// If engine has been busy for >10 min, it's stuck — force release
|
|
347
|
-
if (engineBusySince && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
348
|
-
console.log(`[engine] Force-releasing stuck engine lock (busy for ${Math.round((Date.now() - engineBusySince) / 60000)}min)`);
|
|
349
|
-
engineBusy = false;
|
|
350
|
-
engineBusySince = 0;
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
if (Date.now() > deadline) {
|
|
354
|
-
return { success: false, error: "Engine busy timeout (5 min)" };
|
|
355
|
-
}
|
|
356
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
356
|
+
try {
|
|
357
|
+
await engineQueue.acquire(req.priority, ENGINE_WAIT_DEADLINE_MS);
|
|
357
358
|
}
|
|
358
|
-
|
|
359
|
-
|
|
359
|
+
catch (err) {
|
|
360
|
+
return { success: false, error: err.message || "Engine busy timeout" };
|
|
361
|
+
}
|
|
362
|
+
const prompt = req.context
|
|
363
|
+
? `${req.context}\n\n---\n\n${req.question}`
|
|
364
|
+
: req.question;
|
|
365
|
+
const abortController = new AbortController();
|
|
366
|
+
const timer = setTimeout(() => abortController.abort(), ENGINE_EXEC_TIMEOUT_MS);
|
|
360
367
|
try {
|
|
361
|
-
const
|
|
362
|
-
? `${req.context}\n\n---\n\n${req.question}`
|
|
363
|
-
: req.question;
|
|
364
|
-
// Hard timeout: if engine doesn't respond in 8 min, give up
|
|
365
|
-
const engineTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error("Engine execution timeout (8 min)")), 8 * 60 * 1000));
|
|
366
|
-
const response = await Promise.race([
|
|
367
|
-
runEngine(options.engine || "claude", options.model, options.allowAll, prompt, workdir, req.tools, req.relay),
|
|
368
|
-
engineTimeout,
|
|
369
|
-
]);
|
|
370
|
-
// Track token usage via EventBus
|
|
368
|
+
const response = await runEngine(options.engine || "claude", options.model, options.allowAll, prompt, workdir, req.tools, req.relay, abortController.signal);
|
|
371
369
|
emitTokenUsage(prompt.length, response.length);
|
|
372
370
|
return { success: true, response };
|
|
373
371
|
}
|
|
374
372
|
catch (err) {
|
|
375
|
-
|
|
373
|
+
const msg = abortController.signal.aborted
|
|
374
|
+
? `Engine execution timeout (${Math.round(ENGINE_EXEC_TIMEOUT_MS / 60000)} min)`
|
|
375
|
+
: (err.message || String(err));
|
|
376
|
+
return { success: false, error: msg };
|
|
376
377
|
}
|
|
377
378
|
finally {
|
|
378
|
-
|
|
379
|
-
|
|
379
|
+
clearTimeout(timer);
|
|
380
|
+
engineQueue.release();
|
|
380
381
|
}
|
|
381
382
|
}
|
|
382
383
|
const moduleCtx = {
|
|
@@ -505,8 +506,13 @@ export async function serveStdio(agentName, workdir) {
|
|
|
505
506
|
promptOwner,
|
|
506
507
|
runCollaborativeQuery: (task, selfName, relayHttp, engine, model, allowAll, workdir, relay) => runCollaborativeQuery(task, selfName, relayHttp, engine, model, allowAll, workdir, runEngine, relay),
|
|
507
508
|
autoRoute,
|
|
508
|
-
isEngineBusy: () =>
|
|
509
|
-
setEngineBusy: (busy) => {
|
|
509
|
+
isEngineBusy: () => engineQueue.isBusy(),
|
|
510
|
+
setEngineBusy: (busy) => {
|
|
511
|
+
if (busy)
|
|
512
|
+
engineQueue.tryAcquire();
|
|
513
|
+
else
|
|
514
|
+
engineQueue.release();
|
|
515
|
+
},
|
|
510
516
|
emitTaskCompleted,
|
|
511
517
|
};
|
|
512
518
|
const mcpServer = createMcpServer({ workdir: dir, agentName, publisherIds: new Map() }, stdioMcpDeps);
|
package/dist/task-module.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { readFile } from "fs/promises";
|
|
12
12
|
import { SIG, sig } from "./types.js";
|
|
13
13
|
import { selfDir, biosPath, localNow, loadBioState, saveBioState, syncEnergyFromTokens, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, loadDirectives, buildDirectivesPrompt, appendTaskHistory, notifyOwner, updateHungerDecay, updateNaturalDecay, resetTokenCountIfNewDay, computeSociability, appendBioEvent, bioStatePromptModifier, feedHunger, SHOP_ITEMS, logBioStatus, logBioDecision, } from "./self.js";
|
|
14
|
-
import { appendMessage
|
|
14
|
+
import { appendMessage } from "./context.js";
|
|
15
15
|
import { buildRoleContext } from "./role-module.js";
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// Config
|
|
@@ -254,7 +254,7 @@ export class TaskModule {
|
|
|
254
254
|
const filtered = [];
|
|
255
255
|
for (const item of items) {
|
|
256
256
|
const itemLabel = item.type === "order"
|
|
257
|
-
? `order:${item.data.product_name || item.data.
|
|
257
|
+
? `order:${item.data.product_name || item.data.buyer_name || item.id}`
|
|
258
258
|
: item.type === "user_task" ? `user_task:${item.data.key || item.id}` : `relay_task:${item.id}`;
|
|
259
259
|
const isUrgent = item.quadrant === 1;
|
|
260
260
|
// Fear avoidance (urgent items bypass)
|
|
@@ -296,7 +296,7 @@ export class TaskModule {
|
|
|
296
296
|
return;
|
|
297
297
|
const { workdir, agentName, bus } = this.ctx;
|
|
298
298
|
const relay = this.getRelay();
|
|
299
|
-
const orderLabel = `order:${order.product_name || order.
|
|
299
|
+
const orderLabel = `order:${order.product_name || order.buyer_name || order.id}`;
|
|
300
300
|
const orderPrice = order.price || order.offer_price || 1;
|
|
301
301
|
const startTime = Date.now();
|
|
302
302
|
try {
|
|
@@ -323,7 +323,7 @@ Relay API (use curl with -H "Authorization: Bearer ${this.secretKey}" -H "Conten
|
|
|
323
323
|
Accept order: POST ${this.relayHttp}/v1/orders/${order.id}/accept
|
|
324
324
|
Deliver order: POST ${this.relayHttp}/v1/orders/${order.id}/deliver -d '{"result":"your response"}'
|
|
325
325
|
Extend order: PUT ${this.relayHttp}/v1/orders/${order.id}/extend`;
|
|
326
|
-
const question = `[Order id=${order.id} status=${order.status}] ${order.product_name ? `Product: ${order.product_name}\n` : ""}Buyer: ${order.
|
|
326
|
+
const question = `[Order id=${order.id} status=${order.status}] ${order.product_name ? `Product: ${order.product_name}\n` : ""}Buyer: ${order.buyer_name || order.buyer_ip || "?"}\nRequest: ${order.buyer_task || "(no specific request)"}
|
|
327
327
|
|
|
328
328
|
Steps:
|
|
329
329
|
1. If order status is "pending", accept it first (POST .../accept)
|
|
@@ -332,8 +332,12 @@ Steps:
|
|
|
332
332
|
|
|
333
333
|
RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
|
|
334
334
|
// Write user message to conversation immediately (before engine runs)
|
|
335
|
-
|
|
336
|
-
const
|
|
335
|
+
// buyer_name = agent name (from JOIN), buyer_ip = publisher ID or IP
|
|
336
|
+
const orderBuyer = order.buyer_name || order.buyer_ip || "anonymous";
|
|
337
|
+
// Product orders get isolated conversations; ad-hoc chats share one conv per buyer
|
|
338
|
+
const buyerPubId = orderBuyer;
|
|
339
|
+
const productScope = order.product_id ? `:prod_${order.product_id}` : "";
|
|
340
|
+
const orderConvId = `pub_${buyerPubId}${productScope}`;
|
|
337
341
|
const orderUserMsg = order.buyer_task || "(no message)";
|
|
338
342
|
await appendMessage(workdir, agentName, orderConvId, "User", orderUserMsg);
|
|
339
343
|
console.log(`[task] Fulfilling order ${order.id}...`);
|
|
@@ -553,7 +557,7 @@ Complete this task. Use the environment info above and tools (curl, etc.) as nee
|
|
|
553
557
|
const result = await this.ctx.requestCompute({
|
|
554
558
|
context,
|
|
555
559
|
question,
|
|
556
|
-
priority: "
|
|
560
|
+
priority: "low",
|
|
557
561
|
tools: ["Bash(curl *)"],
|
|
558
562
|
relay: this.relayHttp ? { http: this.relayHttp, agentName } : undefined,
|
|
559
563
|
});
|