deepclause-sdk 0.1.2 → 0.2.1
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/README.md +45 -6
- package/dist/cli/config.d.ts +2 -2
- package/dist/cli/repl.d.ts +55 -4
- package/dist/cli/repl.d.ts.map +1 -1
- package/dist/cli/repl.js +675 -52
- package/dist/cli/repl.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/prolog-src/deepclause_mi.pl +516 -2
- package/dist/repl/backend.d.ts +9 -0
- package/dist/repl/backend.d.ts.map +1 -1
- package/dist/repl/backend.js +62 -5
- package/dist/repl/backend.js.map +1 -1
- package/dist/repl/harness-tools.d.ts +36 -0
- package/dist/repl/harness-tools.d.ts.map +1 -1
- package/dist/repl/harness-tools.js +173 -0
- package/dist/repl/harness-tools.js.map +1 -1
- package/dist/repl/history.d.ts +29 -0
- package/dist/repl/history.d.ts.map +1 -1
- package/dist/repl/history.js +71 -0
- package/dist/repl/history.js.map +1 -1
- package/dist/repl/index.d.ts +6 -0
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +9 -1
- package/dist/repl/index.js.map +1 -1
- package/dist/repl/picker.d.ts +3 -0
- package/dist/repl/picker.d.ts.map +1 -1
- package/dist/repl/picker.js +132 -0
- package/dist/repl/picker.js.map +1 -1
- package/dist/repl/process-backend.d.ts +31 -0
- package/dist/repl/process-backend.d.ts.map +1 -0
- package/dist/repl/process-backend.js +10 -0
- package/dist/repl/process-backend.js.map +1 -0
- package/dist/repl/process-executor.d.ts +67 -0
- package/dist/repl/process-executor.d.ts.map +1 -0
- package/dist/repl/process-executor.js +371 -0
- package/dist/repl/process-executor.js.map +1 -0
- package/dist/repl/process-worker.d.ts +10 -0
- package/dist/repl/process-worker.d.ts.map +1 -0
- package/dist/repl/process-worker.js +210 -0
- package/dist/repl/process-worker.js.map +1 -0
- package/dist/repl/processes.d.ts +282 -0
- package/dist/repl/processes.d.ts.map +1 -0
- package/dist/repl/processes.js +557 -0
- package/dist/repl/processes.js.map +1 -0
- package/dist/repl/render.d.ts +52 -2
- package/dist/repl/render.d.ts.map +1 -1
- package/dist/repl/render.js +52 -17
- package/dist/repl/render.js.map +1 -1
- package/dist/repl/session.d.ts +135 -3
- package/dist/repl/session.d.ts.map +1 -1
- package/dist/repl/session.js +413 -4
- package/dist/repl/session.js.map +1 -1
- package/dist/repl/status.d.ts +3 -1
- package/dist/repl/status.d.ts.map +1 -1
- package/dist/repl/status.js +4 -0
- package/dist/repl/status.js.map +1 -1
- package/dist/repl/tui/autocomplete.d.ts +19 -0
- package/dist/repl/tui/autocomplete.d.ts.map +1 -0
- package/dist/repl/tui/autocomplete.js +48 -0
- package/dist/repl/tui/autocomplete.js.map +1 -0
- package/dist/repl/tui/document.d.ts +51 -0
- package/dist/repl/tui/document.d.ts.map +1 -0
- package/dist/repl/tui/document.js +160 -0
- package/dist/repl/tui/document.js.map +1 -0
- package/dist/repl/tui/harness-view.d.ts +102 -0
- package/dist/repl/tui/harness-view.d.ts.map +1 -0
- package/dist/repl/tui/harness-view.js +323 -0
- package/dist/repl/tui/harness-view.js.map +1 -0
- package/dist/repl/tui/picker.d.ts +31 -0
- package/dist/repl/tui/picker.d.ts.map +1 -0
- package/dist/repl/tui/picker.js +43 -0
- package/dist/repl/tui/picker.js.map +1 -0
- package/dist/repl/tui/renderer.d.ts +98 -0
- package/dist/repl/tui/renderer.d.ts.map +1 -0
- package/dist/repl/tui/renderer.js +289 -0
- package/dist/repl/tui/renderer.js.map +1 -0
- package/dist/repl/tui/services-panel.d.ts +35 -0
- package/dist/repl/tui/services-panel.d.ts.map +1 -0
- package/dist/repl/tui/services-panel.js +108 -0
- package/dist/repl/tui/services-panel.js.map +1 -0
- package/dist/repl/tui/status-bar.d.ts +6 -2
- package/dist/repl/tui/status-bar.d.ts.map +1 -1
- package/dist/repl/tui/status-bar.js +82 -24
- package/dist/repl/tui/status-bar.js.map +1 -1
- package/dist/repl/tui/terminal.d.ts +28 -0
- package/dist/repl/tui/terminal.d.ts.map +1 -0
- package/dist/repl/tui/terminal.js +94 -0
- package/dist/repl/tui/terminal.js.map +1 -0
- package/dist/repl/tui/theme.d.ts +60 -0
- package/dist/repl/tui/theme.d.ts.map +1 -1
- package/dist/repl/tui/theme.js +142 -0
- package/dist/repl/tui/theme.js.map +1 -1
- package/dist/repl/tui/tool-pane.d.ts +68 -0
- package/dist/repl/tui/tool-pane.d.ts.map +1 -0
- package/dist/repl/tui/tool-pane.js +261 -0
- package/dist/repl/tui/tool-pane.js.map +1 -0
- package/dist/repl/tui/working-line.d.ts +23 -0
- package/dist/repl/tui/working-line.d.ts.map +1 -0
- package/dist/repl/tui/working-line.js +58 -0
- package/dist/repl/tui/working-line.js.map +1 -0
- package/dist/repl/worker-host.d.ts +80 -0
- package/dist/repl/worker-host.d.ts.map +1 -0
- package/dist/repl/worker-host.js +305 -0
- package/dist/repl/worker-host.js.map +1 -0
- package/dist/repl/worker-protocol.d.ts +123 -0
- package/dist/repl/worker-protocol.d.ts.map +1 -0
- package/dist/repl/worker-protocol.js +9 -0
- package/dist/repl/worker-protocol.js.map +1 -0
- package/dist/runner.d.ts +24 -0
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +47 -0
- package/dist/runner.js.map +1 -1
- package/dist/system/assets/docs/AGENT_PROMPT.md +193 -0
- package/dist/system/assets/docs/DML_AUTHORING.md +21 -0
- package/dist/system/assets/docs/DML_REFERENCE.md +145 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/prolog-src/deepclause_mi.pl +516 -2
package/dist/repl/session.js
CHANGED
|
@@ -13,16 +13,95 @@ import { loadProlog, mountWorkspace, takePrologMessages, } from '../prolog/loade
|
|
|
13
13
|
import { describeShellExecutionBackend, } from '../system/runtime/shell-manager.js';
|
|
14
14
|
import { getWorkspaceDocAssetPath } from '../system/assets/index.js';
|
|
15
15
|
import { DMLRunner, toJsValue } from '../runner.js';
|
|
16
|
+
import { DmlProcessExecutor } from './process-executor.js';
|
|
17
|
+
import { pidKey } from './processes.js';
|
|
16
18
|
import { compactMemory, estimateMemoryTokens, shouldCompact, toCompactionEvent, } from './compaction.js';
|
|
17
19
|
import { HarnessAgent } from './agent.js';
|
|
18
20
|
import { BRIEF_VALUE_CHARS, createHarnessTools } from './harness-tools.js';
|
|
19
21
|
import { buildParamsTerm, plainValue, quotePrologString } from './frames.js';
|
|
20
22
|
import { buildHarnessHeader, } from './export.js';
|
|
21
|
-
import { HistoryStore, harnessHash, } from './history.js';
|
|
23
|
+
import { HistoryStore, harnessHash, moduleVersion, } from './history.js';
|
|
22
24
|
import { buildDiagramPrompt, extractMermaid, parseMermaidFlowchart, rasterizeSvg, renderFlowchartSvg, renderMermaidAscii, resolveMedia, validateMermaid, } from '../visualize/index.js';
|
|
23
25
|
import { generateLlmReply } from '../prolog/bridge.js';
|
|
24
26
|
import { formatModelCatalogEntry } from './model-resolver.js';
|
|
25
27
|
let sessionCounter = 0;
|
|
28
|
+
/**
|
|
29
|
+
* One human, one editor: human-channel requests from the foreground and from any
|
|
30
|
+
* subagent are served strictly in order (see `requireInput`).
|
|
31
|
+
*/
|
|
32
|
+
let sharedInputQueue = Promise.resolve();
|
|
33
|
+
/** Rebuild a `DMLEvent` from a persisted history record, or undefined. */
|
|
34
|
+
function historyEventFromRecord(record) {
|
|
35
|
+
const type = typeof record.type === 'string' ? record.type : undefined;
|
|
36
|
+
if (!type)
|
|
37
|
+
return undefined;
|
|
38
|
+
const frameKind = typeof record.frame === 'string' ? record.frame : undefined;
|
|
39
|
+
const content = typeof record.content === 'string' ? record.content : undefined;
|
|
40
|
+
switch (type) {
|
|
41
|
+
case 'stream':
|
|
42
|
+
return content ? { type, frameKind, content } : undefined;
|
|
43
|
+
case 'output':
|
|
44
|
+
case 'answer':
|
|
45
|
+
case 'error':
|
|
46
|
+
return { type, frameKind, content };
|
|
47
|
+
case 'log':
|
|
48
|
+
return { type, frameKind, content, level: record.level };
|
|
49
|
+
case 'tool_call':
|
|
50
|
+
return {
|
|
51
|
+
type,
|
|
52
|
+
frameKind,
|
|
53
|
+
toolName: typeof record.toolName === 'string' ? record.toolName : undefined,
|
|
54
|
+
toolState: record.toolState,
|
|
55
|
+
toolSummary: typeof record.toolSummary === 'string' ? record.toolSummary : undefined,
|
|
56
|
+
toolArgs: record.toolArgs,
|
|
57
|
+
};
|
|
58
|
+
case 'solution':
|
|
59
|
+
return {
|
|
60
|
+
type,
|
|
61
|
+
frameKind,
|
|
62
|
+
solution: {
|
|
63
|
+
bindings: (Array.isArray(record.bindings) ? record.bindings : []),
|
|
64
|
+
deterministic: false,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
case 'task_activity':
|
|
68
|
+
return {
|
|
69
|
+
type,
|
|
70
|
+
frameKind,
|
|
71
|
+
taskState: record.taskState,
|
|
72
|
+
taskDescription: typeof record.taskDescription === 'string' ? record.taskDescription : undefined,
|
|
73
|
+
};
|
|
74
|
+
default:
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** The log-view line for an event, or undefined for per-tick noise. */
|
|
79
|
+
function processLogText(event) {
|
|
80
|
+
switch (event.type) {
|
|
81
|
+
case 'started':
|
|
82
|
+
return 'started';
|
|
83
|
+
case 'output':
|
|
84
|
+
case 'stream':
|
|
85
|
+
case 'reasoning':
|
|
86
|
+
case 'tool':
|
|
87
|
+
case 'model':
|
|
88
|
+
return event.content ?? '';
|
|
89
|
+
case 'answer': {
|
|
90
|
+
// A process's terminal result is its accumulated output; the actual
|
|
91
|
+
// answer/1 value is the last non-empty line, so use that (and avoid
|
|
92
|
+
// duplicating the whole transcript in one log row).
|
|
93
|
+
const text = String(event.answer ?? 'ok');
|
|
94
|
+
const lines = text.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
95
|
+
return `› ${lines.length > 0 ? lines[lines.length - 1] : text}`;
|
|
96
|
+
}
|
|
97
|
+
case 'exited':
|
|
98
|
+
if (event.state === 'done')
|
|
99
|
+
return 'finished';
|
|
100
|
+
return `${event.state}${event.error ? ` (${event.error})` : event.reason && event.reason !== 'normal' ? ` (${String(event.reason)})` : ''}`;
|
|
101
|
+
default:
|
|
102
|
+
return undefined; // state/activity are shown in the summary view
|
|
103
|
+
}
|
|
104
|
+
}
|
|
26
105
|
function nextSessionId() {
|
|
27
106
|
sessionCounter += 1;
|
|
28
107
|
return `repl_${Date.now().toString(36)}_${sessionCounter}`;
|
|
@@ -83,6 +162,23 @@ export class DMLSession {
|
|
|
83
162
|
/** Full resolution for the backend, for the agent's environment block. */
|
|
84
163
|
executionEnvironment;
|
|
85
164
|
autoLoadHarness;
|
|
165
|
+
/** Original options, used to create per-process child sessions. */
|
|
166
|
+
openOptions;
|
|
167
|
+
/** Session-scoped subagents (spec §2). */
|
|
168
|
+
processBackend;
|
|
169
|
+
processBackendPromise;
|
|
170
|
+
/**
|
|
171
|
+
* The harness snapshot new processes should run. On the main session this is
|
|
172
|
+
* unset (children snapshot the live module); a worker host sets it to the
|
|
173
|
+
* snapshot the main sent, so its children never run a stale module.
|
|
174
|
+
*/
|
|
175
|
+
processSnapshot;
|
|
176
|
+
processEventSink;
|
|
177
|
+
/** Bounded log of subagent output for the `/ps --watch --logs` view. */
|
|
178
|
+
processLogEntries = [];
|
|
179
|
+
static PROCESS_LOG_LIMIT = 500;
|
|
180
|
+
/** Service declarations replayed from the session record (resume only). */
|
|
181
|
+
recordedServices = [];
|
|
86
182
|
/** Monotonic per-session turn counter used by history records. */
|
|
87
183
|
turnCounter = 0;
|
|
88
184
|
historyWarned = false;
|
|
@@ -109,6 +205,7 @@ export class DMLSession {
|
|
|
109
205
|
this.executionBackend = options.executionBackend ?? 'clean-room';
|
|
110
206
|
this.executionEnvironment = describeShellExecutionBackend(this.executionBackend === 'agentvm', this.executionBackend === 'agentvm' ? {} : { wrapper: this.executionBackend });
|
|
111
207
|
this.autoLoadHarness = options.autoLoadHarness ?? true;
|
|
208
|
+
this.openOptions = options;
|
|
112
209
|
this.compactionSettings = options.compaction?.settings;
|
|
113
210
|
this.compactionAuto = options.compaction?.auto ?? false;
|
|
114
211
|
this.contextWindow = options.contextWindow;
|
|
@@ -144,6 +241,7 @@ export class DMLSession {
|
|
|
144
241
|
}
|
|
145
242
|
const store = new HistoryStore(options.workspacePath ?? process.cwd(), session.sessionId, options.history, session.harnessName);
|
|
146
243
|
snapshot = await store.readSessionHarness();
|
|
244
|
+
session.recordedServices = await store.readServiceRecords();
|
|
147
245
|
if (!snapshot) {
|
|
148
246
|
throw new Error(`session not found or has no saved harness: ${session.sessionId}`);
|
|
149
247
|
}
|
|
@@ -191,6 +289,299 @@ export class DMLSession {
|
|
|
191
289
|
getSessionId() {
|
|
192
290
|
return this.sessionId;
|
|
193
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* The live harness, for the side view: what predicates exist right now, how big
|
|
294
|
+
* each is, and what the open transaction has touched.
|
|
295
|
+
*
|
|
296
|
+
* Read from the session module rather than from the last commit, so a side view
|
|
297
|
+
* shows the harness *being built* — including uncommitted edits.
|
|
298
|
+
*/
|
|
299
|
+
harnessView() {
|
|
300
|
+
const result = this.prologQuery(`deepclause_mi:repl_harness_view('${this.sessionId}', Rows)`);
|
|
301
|
+
const raw = toJsValue(result?.Rows);
|
|
302
|
+
const predicates = Array.isArray(raw)
|
|
303
|
+
? raw
|
|
304
|
+
.map((row) => String(row).split('|'))
|
|
305
|
+
.filter((parts) => parts.length === 3)
|
|
306
|
+
.map(([indicator, clauses, flags]) => {
|
|
307
|
+
const [name, arityText] = indicator.split('/');
|
|
308
|
+
return {
|
|
309
|
+
indicator: indicator.trim(),
|
|
310
|
+
name: String(name).trim(),
|
|
311
|
+
arity: Number(arityText),
|
|
312
|
+
clauses: Number(clauses),
|
|
313
|
+
flags: String(flags).split(',').map((flag) => flag.trim()).filter(Boolean),
|
|
314
|
+
};
|
|
315
|
+
})
|
|
316
|
+
: [];
|
|
317
|
+
const versionResult = this.prologQuery(`( current_predicate(${this.sessionId}:harness_head/4)`
|
|
318
|
+
+ ` -> ${this.sessionId}:harness_head(V, _, _, _) ; V = none )`);
|
|
319
|
+
const version = plainValue(versionResult?.V);
|
|
320
|
+
return {
|
|
321
|
+
predicates: predicates.sort((a, b) => a.indicator.localeCompare(b.indicator)),
|
|
322
|
+
version: version === 'none' ? undefined : Number(version),
|
|
323
|
+
transactionOpen: this.transactionBefore !== null,
|
|
324
|
+
touched: this.editTouched(),
|
|
325
|
+
lastCommit: this.lastTurnCommitted,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
// ---- Session-scoped subagents (spec §2) --------------------------------
|
|
329
|
+
/** Register the sink that receives process lifecycle and output events. */
|
|
330
|
+
setProcessEventSink(sink) {
|
|
331
|
+
this.processEventSink = sink;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* The process backend: in-process (L1) or worker-backed (L2). Created lazily
|
|
335
|
+
* so a session with no subagents pays nothing.
|
|
336
|
+
*/
|
|
337
|
+
ensureProcessBackend() {
|
|
338
|
+
if (this.processBackendPromise)
|
|
339
|
+
return this.processBackendPromise;
|
|
340
|
+
const mode = this.openOptions.execution ?? 'worker';
|
|
341
|
+
this.processBackendPromise = (async () => {
|
|
342
|
+
const onEvent = (event) => {
|
|
343
|
+
this.processEventSink?.(event);
|
|
344
|
+
this.recordServiceEvent(event);
|
|
345
|
+
};
|
|
346
|
+
if (mode === 'worker') {
|
|
347
|
+
const { RemoteProcessHost } = await import('./worker-host.js');
|
|
348
|
+
const host = new RemoteProcessHost(this, { onEvent });
|
|
349
|
+
await host.start();
|
|
350
|
+
this.processBackend = host;
|
|
351
|
+
return host;
|
|
352
|
+
}
|
|
353
|
+
const executor = new DmlProcessExecutor(this, { onEvent });
|
|
354
|
+
this.processBackend = executor;
|
|
355
|
+
return executor;
|
|
356
|
+
})();
|
|
357
|
+
return this.processBackendPromise;
|
|
358
|
+
}
|
|
359
|
+
/** Record a process declaration/exit so a resumed session can replay it (§7). */
|
|
360
|
+
recordServiceEvent(event) {
|
|
361
|
+
this.recordProcessLog(event);
|
|
362
|
+
if (!this.history)
|
|
363
|
+
return;
|
|
364
|
+
if (event.type === 'started') {
|
|
365
|
+
const info = this.processBackend?.info(event.pid);
|
|
366
|
+
void this.history.appendServiceRecord({
|
|
367
|
+
type: 'declare',
|
|
368
|
+
at: event.at,
|
|
369
|
+
pid: event.pid,
|
|
370
|
+
name: event.name,
|
|
371
|
+
goal: info?.goal,
|
|
372
|
+
priority: info?.priority,
|
|
373
|
+
codeVersion: info?.codeVersion,
|
|
374
|
+
harnessVersion: info?.harnessVersion,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
else if (event.type === 'exited') {
|
|
378
|
+
void this.history.appendServiceRecord({
|
|
379
|
+
type: 'exit',
|
|
380
|
+
at: event.at,
|
|
381
|
+
pid: event.pid,
|
|
382
|
+
name: event.name,
|
|
383
|
+
state: event.state,
|
|
384
|
+
reason: event.reason,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/** Service declarations replayed from the session record (resume only). */
|
|
389
|
+
recordedProcesses() {
|
|
390
|
+
return this.recordedServices;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Recent subagent output for the log view, oldest first. Answers and exits are
|
|
394
|
+
* included; per-tick state/activity updates are not (the summary shows those).
|
|
395
|
+
*/
|
|
396
|
+
processLog(limit = 200) {
|
|
397
|
+
const entries = this.processLogEntries;
|
|
398
|
+
return limit >= entries.length ? [...entries] : entries.slice(entries.length - limit);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Events from the resumed session, to render it on load. Empty unless this
|
|
402
|
+
* session was opened with `resume` and history is enabled.
|
|
403
|
+
*/
|
|
404
|
+
async resumedTranscript(limit = 400) {
|
|
405
|
+
if (!this.resume || !this.history)
|
|
406
|
+
return [];
|
|
407
|
+
const records = await this.history.readEvents(this.sessionId, limit);
|
|
408
|
+
return records
|
|
409
|
+
.map((record) => historyEventFromRecord(record))
|
|
410
|
+
.filter((event) => Boolean(event));
|
|
411
|
+
}
|
|
412
|
+
recordProcessLog(event) {
|
|
413
|
+
const text = processLogText(event);
|
|
414
|
+
if (text === undefined)
|
|
415
|
+
return;
|
|
416
|
+
// Coalesce streamed chunks into one growing line per process/kind.
|
|
417
|
+
const last = this.processLogEntries[this.processLogEntries.length - 1];
|
|
418
|
+
const streamy = event.type === 'stream' || event.type === 'reasoning';
|
|
419
|
+
if (streamy && last && last.kind === event.type && pidKey(last.pid) === pidKey(event.pid) && !last.done) {
|
|
420
|
+
last.text = (last.text + text).slice(-2000);
|
|
421
|
+
last.done = event.done;
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
this.processLogEntries.push({
|
|
425
|
+
at: event.at,
|
|
426
|
+
pid: event.pid,
|
|
427
|
+
name: event.name,
|
|
428
|
+
kind: event.type,
|
|
429
|
+
text,
|
|
430
|
+
done: event.done,
|
|
431
|
+
});
|
|
432
|
+
if (this.processLogEntries.length > DMLSession.PROCESS_LOG_LIMIT) {
|
|
433
|
+
this.processLogEntries.splice(0, this.processLogEntries.length - DMLSession.PROCESS_LOG_LIMIT);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/** Start a session-scoped subagent running `goal`; returns immediately. */
|
|
437
|
+
async spawnProcess(goal, options = {}) {
|
|
438
|
+
return (await this.ensureProcessBackend()).spawn(goal, options);
|
|
439
|
+
}
|
|
440
|
+
/** The whole system state: every process this session owns. */
|
|
441
|
+
processes() {
|
|
442
|
+
return this.processBackend?.processes() ?? [];
|
|
443
|
+
}
|
|
444
|
+
processInfo(pid) {
|
|
445
|
+
return this.processBackend?.info(pid);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The harness source a process is running, for inspection. Accepts a pid or a
|
|
449
|
+
* registered name. Undefined when the process is unknown.
|
|
450
|
+
*/
|
|
451
|
+
processCode(target) {
|
|
452
|
+
const pid = typeof target === 'string' ? this.processBackend?.whereis(target) : target;
|
|
453
|
+
return pid ? this.processBackend?.codeOf(pid) : undefined;
|
|
454
|
+
}
|
|
455
|
+
/** The version of the harness the session itself is currently editing. */
|
|
456
|
+
currentHarnessVersion() {
|
|
457
|
+
return this.processSnapshotFor().version;
|
|
458
|
+
}
|
|
459
|
+
/** Stop one process by pid or registered name. */
|
|
460
|
+
async stopProcess(target, reason = 'shutdown') {
|
|
461
|
+
await (await this.ensureProcessBackend()).stop(target, reason);
|
|
462
|
+
}
|
|
463
|
+
/** Resolve a registered process name. */
|
|
464
|
+
whereisProcess(name) {
|
|
465
|
+
return this.processBackend?.whereis(name);
|
|
466
|
+
}
|
|
467
|
+
/** Register a process under a session-scoped name. */
|
|
468
|
+
registerProcess(name, pid) {
|
|
469
|
+
this.processBackend?.register(name, pid);
|
|
470
|
+
}
|
|
471
|
+
/** Copy a message term into a process's mailbox. */
|
|
472
|
+
sendProcess(to, messageTerm) {
|
|
473
|
+
this.processBackend?.send(to, messageTerm);
|
|
474
|
+
}
|
|
475
|
+
/** Request/reply against a service process (library(service) protocol). */
|
|
476
|
+
async callProcess(to, messageTerm, timeoutMs = 300_000) {
|
|
477
|
+
return (await this.ensureProcessBackend()).callTerm(to, messageTerm, timeoutMs);
|
|
478
|
+
}
|
|
479
|
+
/** Resolve a process primitive for this (non-process) foreground run. */
|
|
480
|
+
async handleForeground(request, signal) {
|
|
481
|
+
return (await this.ensureProcessBackend()).foregroundHandle(request, signal);
|
|
482
|
+
}
|
|
483
|
+
/** Host tool descriptors, for a worker's proxy tools. */
|
|
484
|
+
toolDescriptors() {
|
|
485
|
+
return [...this.tools.entries()].map(([name, tool]) => ({
|
|
486
|
+
name,
|
|
487
|
+
description: tool.description,
|
|
488
|
+
parameters: tool.parameters,
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
/** Run a model call on behalf of a worker's proxy backend. */
|
|
492
|
+
async completeModelRequest(request) {
|
|
493
|
+
if (!this.llmBackend)
|
|
494
|
+
throw new Error('this session has no LLM backend');
|
|
495
|
+
return this.llmBackend.complete(request);
|
|
496
|
+
}
|
|
497
|
+
/** Run a host tool on behalf of a worker's proxy tool. */
|
|
498
|
+
runHostTool(name, args, signal) {
|
|
499
|
+
const tool = this.tools.get(name);
|
|
500
|
+
if (!tool)
|
|
501
|
+
throw new Error(`unknown host tool: ${name}`);
|
|
502
|
+
return tool.execute(args, { signal });
|
|
503
|
+
}
|
|
504
|
+
/** Prompt the human on behalf of a worker's `input/2`. */
|
|
505
|
+
requestHumanInput(prompt) {
|
|
506
|
+
return this.requireInput(prompt);
|
|
507
|
+
}
|
|
508
|
+
// Accessors for building a worker's init data (see worker-host.ts).
|
|
509
|
+
workspacePathValue() {
|
|
510
|
+
return this.workspacePath;
|
|
511
|
+
}
|
|
512
|
+
modelId() {
|
|
513
|
+
return this.model;
|
|
514
|
+
}
|
|
515
|
+
providerId() {
|
|
516
|
+
return this.provider;
|
|
517
|
+
}
|
|
518
|
+
temperatureValue() {
|
|
519
|
+
return this.temperature;
|
|
520
|
+
}
|
|
521
|
+
maxTokensValue() {
|
|
522
|
+
return this.maxTokens;
|
|
523
|
+
}
|
|
524
|
+
toolPolicyValue() {
|
|
525
|
+
return this.toolPolicy;
|
|
526
|
+
}
|
|
527
|
+
/** The living module as loadable DML source, for a process snapshot. */
|
|
528
|
+
async harnessText() {
|
|
529
|
+
return this.harnessTextSync();
|
|
530
|
+
}
|
|
531
|
+
/** Synchronous form of `harnessText`, for spawn-time stamping. */
|
|
532
|
+
harnessTextSync() {
|
|
533
|
+
const result = this.prologQuery(`deepclause_mi:export_harness('${this.sessionId}', '', Text)`);
|
|
534
|
+
return String(plainValue(result?.Text) ?? '');
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* The harness snapshot and its version for a process about to be spawned.
|
|
538
|
+
*
|
|
539
|
+
* A worker host holds the snapshot the main sent; the main computes it from
|
|
540
|
+
* the live module. Either way, every process carries the exact module it runs
|
|
541
|
+
* and a version that lets two processes be compared at a glance.
|
|
542
|
+
*/
|
|
543
|
+
processSnapshotFor() {
|
|
544
|
+
if (this.processSnapshot)
|
|
545
|
+
return this.processSnapshot;
|
|
546
|
+
const text = this.harnessTextSync();
|
|
547
|
+
return { text, version: moduleVersion(text) };
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Adopt a process snapshot pushed by the main session (worker hosts only).
|
|
551
|
+
* Keeps a worker's children on the code the user actually committed.
|
|
552
|
+
*/
|
|
553
|
+
setProcessSnapshot(text, version) {
|
|
554
|
+
this.processSnapshot = { text, version: version ?? moduleVersion(text) };
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* A private child session carrying a snapshot of this harness (invariant C).
|
|
558
|
+
*
|
|
559
|
+
* Returns the session and, when the snapshot could not be loaded, the load
|
|
560
|
+
* error. It does **not** throw on a load failure: the child still runs its goal
|
|
561
|
+
* (a swarm item reports `failed`, say), whereas throwing would kill the process
|
|
562
|
+
* before it can report and leave a waiting parent hanging.
|
|
563
|
+
*/
|
|
564
|
+
async createProcessSession() {
|
|
565
|
+
const text = this.processSnapshotFor().text;
|
|
566
|
+
const child = await DMLSession.open({
|
|
567
|
+
...this.openOptions,
|
|
568
|
+
execution: 'inline',
|
|
569
|
+
sessionId: undefined,
|
|
570
|
+
resume: false,
|
|
571
|
+
history: undefined,
|
|
572
|
+
autoLoadHarness: false,
|
|
573
|
+
tools: this.tools,
|
|
574
|
+
toolPolicy: this.toolPolicy,
|
|
575
|
+
onInput: this.inputHandler,
|
|
576
|
+
onInputRequired: undefined,
|
|
577
|
+
});
|
|
578
|
+
let loadError;
|
|
579
|
+
if (text.trim()) {
|
|
580
|
+
const loaded = await child.loadCode(text);
|
|
581
|
+
loadError = loaded.error;
|
|
582
|
+
}
|
|
583
|
+
return { session: child, loadError };
|
|
584
|
+
}
|
|
194
585
|
/**
|
|
195
586
|
* Prolog output captured since the last drain.
|
|
196
587
|
*
|
|
@@ -783,7 +1174,7 @@ export class DMLSession {
|
|
|
783
1174
|
* The child frame uses the living scratch module and may itself call task()
|
|
784
1175
|
* (RLM-style recursion); its harness-memory writes are discarded afterward.
|
|
785
1176
|
*/
|
|
786
|
-
async executeGoal(goal, limit = 5, onEvent, signal, bindingMode = 'full') {
|
|
1177
|
+
async executeGoal(goal, limit = 5, onEvent, signal, bindingMode = 'full', process) {
|
|
787
1178
|
const startedAt = Date.now();
|
|
788
1179
|
const elapsed = () => Date.now() - startedAt;
|
|
789
1180
|
const childDepth = this.frameDepth + 1;
|
|
@@ -814,7 +1205,7 @@ export class DMLSession {
|
|
|
814
1205
|
let runError;
|
|
815
1206
|
this.frameDepth = childDepth;
|
|
816
1207
|
try {
|
|
817
|
-
for await (const event of this.runner.runFrame(frame.id, this.buildRunOptions({ signal }))) {
|
|
1208
|
+
for await (const event of this.runner.runFrame(frame.id, this.buildRunOptions({ signal, process }))) {
|
|
818
1209
|
onEvent?.(event);
|
|
819
1210
|
if (event.type === 'solution') {
|
|
820
1211
|
if (solutions.length < limit) {
|
|
@@ -1359,6 +1750,14 @@ export class DMLSession {
|
|
|
1359
1750
|
if (this.disposed)
|
|
1360
1751
|
return;
|
|
1361
1752
|
this.disposed = true;
|
|
1753
|
+
if (this.processBackend) {
|
|
1754
|
+
try {
|
|
1755
|
+
await this.processBackend.shutdown();
|
|
1756
|
+
}
|
|
1757
|
+
catch {
|
|
1758
|
+
// teardown is best-effort
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1362
1761
|
if (this.history) {
|
|
1363
1762
|
try {
|
|
1364
1763
|
await this.persistSessionHarness(this.history.currentVersion);
|
|
@@ -1384,6 +1783,11 @@ export class DMLSession {
|
|
|
1384
1783
|
: async () => undefined,
|
|
1385
1784
|
signal: options.signal,
|
|
1386
1785
|
onModelCall: () => this.reserveModelCall(),
|
|
1786
|
+
// A run that is not itself a process resolves primitives through the
|
|
1787
|
+
// process backend (local or worker); a process uses its own binding.
|
|
1788
|
+
onProcessRequest: options.process
|
|
1789
|
+
? (request, signal) => options.process.handle(request, signal)
|
|
1790
|
+
: (request, signal) => this.handleForeground(request, signal),
|
|
1387
1791
|
};
|
|
1388
1792
|
}
|
|
1389
1793
|
// ---- TypeScript compaction (ported from pi) ----
|
|
@@ -1511,7 +1915,12 @@ export class DMLSession {
|
|
|
1511
1915
|
if (!this.inputHandler) {
|
|
1512
1916
|
return undefined;
|
|
1513
1917
|
}
|
|
1514
|
-
|
|
1918
|
+
// Serialize human-channel requests across every session in this process: a
|
|
1919
|
+
// background subagent's input/2 must not clobber a foreground prompt (one
|
|
1920
|
+
// editor, one human). Child process sessions share this module-level queue.
|
|
1921
|
+
const value = sharedInputQueue.then(() => this.inputHandler(prompt, options));
|
|
1922
|
+
sharedInputQueue = value.then(() => undefined, () => undefined);
|
|
1923
|
+
return value;
|
|
1515
1924
|
}
|
|
1516
1925
|
/**
|
|
1517
1926
|
* Reserve one model call for the current turn. When the budget is reached,
|