apex-code 0.0.4 → 0.0.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/CHANGELOG.md +14 -1
- package/dist/cli/agent-lifecycle.d.ts +24 -0
- package/dist/cli/agent-lifecycle.d.ts.map +1 -0
- package/dist/cli/agent-lifecycle.js +126 -0
- package/dist/cli/agent-lifecycle.js.map +1 -0
- package/dist/cli/args.d.ts +13 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +80 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +160 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +265 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/delegation/runtime.d.ts +682 -1
- package/dist/core/delegation/runtime.d.ts.map +1 -1
- package/dist/core/delegation/runtime.js +1339 -66
- package/dist/core/delegation/runtime.js.map +1 -1
- package/dist/core/sdk.d.ts +61 -3
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +316 -22
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/delegate.d.ts +8 -0
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +33 -3
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/workspace/git-observer.d.ts +16 -0
- package/dist/core/workspace/git-observer.d.ts.map +1 -1
- package/dist/core/workspace/git-observer.js +8 -1
- package/dist/core/workspace/git-observer.js.map +1 -1
- package/dist/core/workspace/git-worktree-owner.d.ts +89 -0
- package/dist/core/workspace/git-worktree-owner.d.ts.map +1 -0
- package/dist/core/workspace/git-worktree-owner.js +240 -0
- package/dist/core/workspace/git-worktree-owner.js.map +1 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +20 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/acp/server.d.ts +43 -1
- package/dist/modes/acp/server.d.ts.map +1 -1
- package/dist/modes/acp/server.js +78 -0
- package/dist/modes/acp/server.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +38 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +110 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
|
@@ -26,6 +26,7 @@ import { createSessionCheckpoints } from "./checkpoints/session-checkpoints.js";
|
|
|
26
26
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, shouldCompact, } from "./compaction/index.js";
|
|
27
27
|
import { evictionBudget, installContextPipeline, isDefaultStreamFunction } from "./context/pipeline.js";
|
|
28
28
|
import { DEFAULT_THINKING_LEVEL, THINKING_LEVEL_OPTIONS } from "./defaults.js";
|
|
29
|
+
import { RESUME_CHILD_PROMPT, runDelegation } from "./delegation/runtime.js";
|
|
29
30
|
import { SessionEvidenceSink } from "./evidence.js";
|
|
30
31
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
31
32
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
@@ -88,6 +89,12 @@ function estimateMessagesTokens(messages) {
|
|
|
88
89
|
return tokens;
|
|
89
90
|
}
|
|
90
91
|
export class AgentSession {
|
|
92
|
+
_lifecycle = "created";
|
|
93
|
+
_activePrompt = false;
|
|
94
|
+
/** Lifecycle is deliberately retained after completion/close for protocol callers. */
|
|
95
|
+
get lifecycle() {
|
|
96
|
+
return this._lifecycle;
|
|
97
|
+
}
|
|
91
98
|
agent;
|
|
92
99
|
sessionManager;
|
|
93
100
|
settingsManager;
|
|
@@ -159,6 +166,9 @@ export class AgentSession {
|
|
|
159
166
|
_checkpoints;
|
|
160
167
|
_hookRuntime;
|
|
161
168
|
_backgroundShellRegistry;
|
|
169
|
+
_childRunRegistry;
|
|
170
|
+
_delegationRuntime;
|
|
171
|
+
_aggregateBudgetUsage;
|
|
162
172
|
_permissionResponderFactory;
|
|
163
173
|
/** Worktree checkpoints for this session. Inert unless `checkpoints.enabled` is set. */
|
|
164
174
|
get checkpoints() {
|
|
@@ -211,6 +221,21 @@ export class AgentSession {
|
|
|
211
221
|
});
|
|
212
222
|
this._hookRuntime = config.hookRuntime;
|
|
213
223
|
this._backgroundShellRegistry = config.backgroundShellRegistry;
|
|
224
|
+
this._childRunRegistry = config.childRunRegistry;
|
|
225
|
+
this._delegationRuntime = config.delegationRuntime;
|
|
226
|
+
this._aggregateBudgetUsage = config.aggregateBudgetUsage;
|
|
227
|
+
if (this._childRunRegistry) {
|
|
228
|
+
// Install persistence before restoring history. Restoration is normally
|
|
229
|
+
// passive, but registries may reconcile lifecycle state while loading;
|
|
230
|
+
// no child transition should be able to observe an unwired registry.
|
|
231
|
+
this._childRunRegistry.setPersistence((record) => this.sessionManager.appendCustomEntry("child_run", record));
|
|
232
|
+
const records = this.sessionManager
|
|
233
|
+
.getEntries()
|
|
234
|
+
.filter((entry) => entry.type === "custom" && entry.customType === "child_run")
|
|
235
|
+
.map((entry) => entry.data)
|
|
236
|
+
.filter((value) => !!value && typeof value === "object");
|
|
237
|
+
this._childRunRegistry.restore(records);
|
|
238
|
+
}
|
|
214
239
|
this._permissionResponderFactory = config.permissionResponderFactory;
|
|
215
240
|
this._workspaceComparePending = hasWorkspaceObservationAwaitingComparison(this.sessionManager);
|
|
216
241
|
// Always subscribe to agent events for internal handling
|
|
@@ -791,6 +816,196 @@ export class AgentSession {
|
|
|
791
816
|
}
|
|
792
817
|
};
|
|
793
818
|
}
|
|
819
|
+
/** Child handles remain available until explicitly closed or this session is disposed. */
|
|
820
|
+
listChildRuns() {
|
|
821
|
+
return this._childRunRegistry?.list() ?? [];
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* A point-in-time snapshot of the root aggregate budget's counters for this
|
|
825
|
+
* session's whole delegation tree -- {providerRequests, toolCalls,
|
|
826
|
+
* maintenanceRequests, startedAtMs} (spec 2026-09-09, "Shared budgets").
|
|
827
|
+
* `undefined` when no aggregate budget is configured for the tree.
|
|
828
|
+
*/
|
|
829
|
+
aggregateBudgetUsage() {
|
|
830
|
+
return this._aggregateBudgetUsage?.();
|
|
831
|
+
}
|
|
832
|
+
_requireChildRuns() {
|
|
833
|
+
if (!this._childRunRegistry)
|
|
834
|
+
throw new Error("Child run registry is unavailable.");
|
|
835
|
+
return this._childRunRegistry;
|
|
836
|
+
}
|
|
837
|
+
waitChildRun(id) {
|
|
838
|
+
return this._requireChildRuns().wait(id);
|
|
839
|
+
}
|
|
840
|
+
retrieveChildRun(id) {
|
|
841
|
+
return this._requireChildRuns().retrieve(id);
|
|
842
|
+
}
|
|
843
|
+
sendChildInput(id, input) {
|
|
844
|
+
return this._requireChildRuns().sendInput(id, input);
|
|
845
|
+
}
|
|
846
|
+
interruptChildRun(id, reason) {
|
|
847
|
+
this._requireChildRuns().interrupt(id, reason);
|
|
848
|
+
}
|
|
849
|
+
closeChildRun(id) {
|
|
850
|
+
this._requireChildRuns().close(id);
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Non-blocking status snapshot for one child run (spec 2026-09-09, pollable
|
|
854
|
+
* status): `{handleId, agentType, task, status, attempt, attempts, ...}` built
|
|
855
|
+
* from the record/entry alone -- this never awaits a turn, so a caller can
|
|
856
|
+
* poll a running child without blocking (`waitChildRun` stays the blocking
|
|
857
|
+
* form). Unknown ids still error; historical records are served from the
|
|
858
|
+
* persisted record. Observing the status lazily interrupts a running child
|
|
859
|
+
* whose wall-clock deadline has passed.
|
|
860
|
+
*/
|
|
861
|
+
childRunStatus(id) {
|
|
862
|
+
return this._requireChildRuns().status(id);
|
|
863
|
+
}
|
|
864
|
+
/**
|
|
865
|
+
* The child run's token/cost totals, rolled up from the child's OWN session
|
|
866
|
+
* transcript (`ChildRunRegistry.usageTotals`). `undefined` -- never a throw,
|
|
867
|
+
* never a zero-fill -- when nothing is reachable (an in-memory child without
|
|
868
|
+
* a reachable transcript, or a legacy record without any transcript).
|
|
869
|
+
*/
|
|
870
|
+
childRunUsageTotals(id) {
|
|
871
|
+
return this._requireChildRuns().usageTotals(id);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Explicitly verify and reactivate a retained child worktree (spec
|
|
875
|
+
* 2026-09-09, "Workspace states and explicit recovery"): the thinnest
|
|
876
|
+
* pass-through over `ChildRunRegistry.recoverWorkspace`. Read-only git
|
|
877
|
+
* inspection through the workspace owner -- the admin entry, the checked-out
|
|
878
|
+
* branch, and the owner's layout -- never a recreate, checkout, reset, or
|
|
879
|
+
* force. On success the workspace state becomes "active" (persisted) and the
|
|
880
|
+
* child can be resumed in the SAME worktree; `{dirty}` reports preserved
|
|
881
|
+
* uncommitted work. Every failed check throws an actionable error naming the
|
|
882
|
+
* failed check and leaving the state unverified.
|
|
883
|
+
*/
|
|
884
|
+
recoverChildWorkspace(id) {
|
|
885
|
+
return this._requireChildRuns().recoverWorkspace(id);
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Start a background child run through the session's own delegation runtime.
|
|
889
|
+
* A thin pass-through over `runDelegation` (registry-backed, always
|
|
890
|
+
* background): admission -- agent resolution, depth bound, capability
|
|
891
|
+
* ceiling -- is the same projection the `delegate` tool uses, so a spawned
|
|
892
|
+
* child can never hold authority the parent cannot cover. Foreground
|
|
893
|
+
* spawning is not offered; protocol clients wait on the returned handle.
|
|
894
|
+
*
|
|
895
|
+
* Idempotent spawn: a supplied `idempotencyKey` that already maps to a
|
|
896
|
+
* handle returns the EXISTING handle with `created: false` and never builds
|
|
897
|
+
* a second child (no budget slot consumed twice); the key persists on the
|
|
898
|
+
* record, so a restarted parent dedupes too. `timeoutMs` caps the child's
|
|
899
|
+
* own run budget at the wall-time gate and records the deadline for lazy
|
|
900
|
+
* status observation.
|
|
901
|
+
*/
|
|
902
|
+
async startChildRun(agentType, task, request) {
|
|
903
|
+
const runtime = this._delegationRuntime;
|
|
904
|
+
if (!runtime) {
|
|
905
|
+
throw new Error("No delegation runtime is configured; create the session with a permission gate or options.delegation to spawn agents.");
|
|
906
|
+
}
|
|
907
|
+
const existing = request?.idempotencyKey !== undefined
|
|
908
|
+
? this._childRunRegistry?.handleForIdempotencyKey(request.idempotencyKey)
|
|
909
|
+
: undefined;
|
|
910
|
+
if (existing !== undefined)
|
|
911
|
+
return { handleId: existing, created: false };
|
|
912
|
+
const result = await runDelegation(runtime, agentType, task, {
|
|
913
|
+
background: true,
|
|
914
|
+
workspace: request?.workspace,
|
|
915
|
+
idempotencyKey: request?.idempotencyKey,
|
|
916
|
+
timeoutMs: request?.timeoutMs,
|
|
917
|
+
});
|
|
918
|
+
if (!result.handleId) {
|
|
919
|
+
throw new Error(`Delegation to agent "${agentType}" did not return a background handle.`);
|
|
920
|
+
}
|
|
921
|
+
return { handleId: result.handleId, created: true };
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* Wait for a child run's latest settled turn and return its payload with the
|
|
925
|
+
* registry status after settlement: `{ status, output, outcome }` plus the
|
|
926
|
+
* run's additive linkage fields (`artifactDir`, `sessionFile`,
|
|
927
|
+
* `parentSessionId`, `policy`, `sandboxEnforced`) when the registry snapshot
|
|
928
|
+
* knows them. A failed settlement surfaces as `outcome: "failed"` with the
|
|
929
|
+
* error message as the output rather than a rejection, so protocol waiters
|
|
930
|
+
* get the same payload shape on every settled turn; unknown handles still
|
|
931
|
+
* throw the registry's actionable error.
|
|
932
|
+
*/
|
|
933
|
+
async waitChildRunResult(id) {
|
|
934
|
+
// Linkage comes from the same registry snapshot every status payload is
|
|
935
|
+
// built from -- never recomputed here. Unknown handles throw through the
|
|
936
|
+
// status read below only after wait itself has rethrown, so the error
|
|
937
|
+
// contract is unchanged.
|
|
938
|
+
const linkageOf = () => {
|
|
939
|
+
try {
|
|
940
|
+
const snapshot = this.childRunStatus(id);
|
|
941
|
+
return {
|
|
942
|
+
...(snapshot.artifactDir !== undefined ? { artifactDir: snapshot.artifactDir } : {}),
|
|
943
|
+
...(snapshot.sessionFile !== undefined ? { sessionFile: snapshot.sessionFile } : {}),
|
|
944
|
+
...(snapshot.parentSessionId !== undefined ? { parentSessionId: snapshot.parentSessionId } : {}),
|
|
945
|
+
...(snapshot.policy ? { policy: snapshot.policy } : {}),
|
|
946
|
+
...(snapshot.sandboxEnforced !== undefined ? { sandboxEnforced: snapshot.sandboxEnforced } : {}),
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
return {};
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
let output;
|
|
954
|
+
let outcome;
|
|
955
|
+
try {
|
|
956
|
+
const result = await this.waitChildRun(id);
|
|
957
|
+
output = result.output;
|
|
958
|
+
outcome = result.outcome ?? "completed";
|
|
959
|
+
}
|
|
960
|
+
catch (error) {
|
|
961
|
+
const status = this.listChildRuns().find((run) => run.handleId === id)?.status;
|
|
962
|
+
if (status === undefined)
|
|
963
|
+
throw error;
|
|
964
|
+
return {
|
|
965
|
+
status,
|
|
966
|
+
output: error instanceof Error ? error.message : String(error),
|
|
967
|
+
outcome: "failed",
|
|
968
|
+
...linkageOf(),
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
const status = this.listChildRuns().find((run) => run.handleId === id)?.status;
|
|
972
|
+
return { status: status ?? "closed", output, outcome, ...linkageOf() };
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Resume an interrupted child run in its existing child session.
|
|
976
|
+
*
|
|
977
|
+
* The thinnest pass-through over the session-owned registry: the child keeps its
|
|
978
|
+
* identity, policy ceiling, and session linkage, and no second service or registry
|
|
979
|
+
* is involved. Returns the observed status after the resumed turn settles. The
|
|
980
|
+
* resumed turn's output becomes the run's stored latest result, so a later
|
|
981
|
+
* `waitChildRun` retrieves it; this call itself returns only the observed status.
|
|
982
|
+
*
|
|
983
|
+
* A handle known only as a persisted record (restored from this session's
|
|
984
|
+
* `child_run` log, no live entry in this process) is historically reattached
|
|
985
|
+
* instead: its recorded child session file is reopened through the same
|
|
986
|
+
* construction seam a live delegation uses, then resumed. Live handles keep
|
|
987
|
+
* the interrupted-only contract below.
|
|
988
|
+
*/
|
|
989
|
+
async resumeChildRun(id, input) {
|
|
990
|
+
const registry = this._requireChildRuns();
|
|
991
|
+
const current = registry.list().find((run) => run.handleId === id)?.status;
|
|
992
|
+
if (current === undefined)
|
|
993
|
+
throw new Error(`Unknown delegation handle "${id}".`);
|
|
994
|
+
if (registry.isHistorical(id))
|
|
995
|
+
return registry.resumeHistorical(id, input);
|
|
996
|
+
if (current !== "interrupted")
|
|
997
|
+
throw new Error(`Child run "${id}" is not interrupted (status: ${current}).`);
|
|
998
|
+
// A resume is a new attempt epoch (spec 2026-09-09, "Child lifecycle"):
|
|
999
|
+
// the interrupted attempt closes with its settled outcome and the resuming
|
|
1000
|
+
// turn opens a new one. Historical resume does the same inside
|
|
1001
|
+
// `resumeHistorical`.
|
|
1002
|
+
registry.beginResumeAttempt(id);
|
|
1003
|
+
await registry.sendInput(id, input ?? RESUME_CHILD_PROMPT);
|
|
1004
|
+
const status = registry.list().find((run) => run.handleId === id)?.status;
|
|
1005
|
+
if (status === undefined)
|
|
1006
|
+
throw new Error(`Child run "${id}" disappeared while resuming.`);
|
|
1007
|
+
return status;
|
|
1008
|
+
}
|
|
794
1009
|
/** Disconnect from agent events during disposal. */
|
|
795
1010
|
_disconnectFromAgent() {
|
|
796
1011
|
if (this._unsubscribeAgent) {
|
|
@@ -803,6 +1018,9 @@ export class AgentSession {
|
|
|
803
1018
|
* Call this when completely done with the session.
|
|
804
1019
|
*/
|
|
805
1020
|
dispose() {
|
|
1021
|
+
if (this._lifecycle === "closed")
|
|
1022
|
+
return;
|
|
1023
|
+
this._lifecycle = "closed";
|
|
806
1024
|
try {
|
|
807
1025
|
this.abortRetry();
|
|
808
1026
|
this.abortCompaction();
|
|
@@ -812,6 +1030,7 @@ export class AgentSession {
|
|
|
812
1030
|
// Background shell children (spec 2026-08-31-background-shell.md) must
|
|
813
1031
|
// never outlive the session that launched them.
|
|
814
1032
|
this._backgroundShellRegistry?.dispose();
|
|
1033
|
+
this._childRunRegistry?.dispose();
|
|
815
1034
|
}
|
|
816
1035
|
catch {
|
|
817
1036
|
// Dispose must succeed even if an abort hook throws.
|
|
@@ -1061,6 +1280,33 @@ export class AgentSession {
|
|
|
1061
1280
|
* @throws Error if no model selected or no API key available (when not streaming)
|
|
1062
1281
|
*/
|
|
1063
1282
|
async prompt(text, options) {
|
|
1283
|
+
if (this._lifecycle === "closed")
|
|
1284
|
+
throw new Error("Session is closed.");
|
|
1285
|
+
if (this._activePrompt) {
|
|
1286
|
+
if (!this.isStreaming)
|
|
1287
|
+
throw new Error("Session is already running.");
|
|
1288
|
+
return this._prompt(text, options);
|
|
1289
|
+
}
|
|
1290
|
+
this._activePrompt = true;
|
|
1291
|
+
this._lifecycle = "running";
|
|
1292
|
+
try {
|
|
1293
|
+
await this._prompt(text, options);
|
|
1294
|
+
if (this.lifecycle === "running") {
|
|
1295
|
+
const last = [...this.agent.state.messages].reverse().find((message) => message.role === "assistant");
|
|
1296
|
+
this._lifecycle =
|
|
1297
|
+
last?.stopReason === "error" ? "failed" : last?.stopReason === "aborted" ? "interrupted" : "completed";
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
catch (error) {
|
|
1301
|
+
if (this.lifecycle === "running")
|
|
1302
|
+
this._lifecycle = "failed";
|
|
1303
|
+
throw error;
|
|
1304
|
+
}
|
|
1305
|
+
finally {
|
|
1306
|
+
this._activePrompt = false;
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async _prompt(text, options) {
|
|
1064
1310
|
const expandPromptTemplates = options?.expandPromptTemplates ?? true;
|
|
1065
1311
|
const preflightResult = options?.preflightResult;
|
|
1066
1312
|
let messages;
|
|
@@ -1190,6 +1436,8 @@ export class AgentSession {
|
|
|
1190
1436
|
preflightResult?.(true);
|
|
1191
1437
|
await this._runWorkspaceComparisonBoundary();
|
|
1192
1438
|
await this._runAgentPrompt(messages);
|
|
1439
|
+
if (this.lifecycle === "closed" || this.lifecycle === "interrupted")
|
|
1440
|
+
return;
|
|
1193
1441
|
await this._runVerificationTurnBoundary();
|
|
1194
1442
|
}
|
|
1195
1443
|
/**
|
|
@@ -1471,6 +1719,8 @@ export class AgentSession {
|
|
|
1471
1719
|
* Abort current operation and wait for agent to become idle.
|
|
1472
1720
|
*/
|
|
1473
1721
|
async abort() {
|
|
1722
|
+
if (this._activePrompt && this._lifecycle !== "closed")
|
|
1723
|
+
this._lifecycle = "interrupted";
|
|
1474
1724
|
this.abortRetry();
|
|
1475
1725
|
this.agent.abort();
|
|
1476
1726
|
await this.waitForIdle();
|
|
@@ -1859,6 +2109,21 @@ export class AgentSession {
|
|
|
1859
2109
|
verificationStatus() {
|
|
1860
2110
|
return this._verificationTracker?.completionStatus() ?? "unavailable";
|
|
1861
2111
|
}
|
|
2112
|
+
/** The configured verification boundary, for execution owners enforcing it. */
|
|
2113
|
+
verificationBoundary() {
|
|
2114
|
+
return this._verificationTracker?.boundary ?? "explicit";
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* The configured verification policy ids, read through the canonical
|
|
2118
|
+
* tracker (refreshed from settings on each call, like requestVerification).
|
|
2119
|
+
* Execution owners such as the delegation child gate use the count to tell
|
|
2120
|
+
* "no policies configured" (turn proceeds unchanged) from "configured and
|
|
2121
|
+
* not verified" (turn fails) -- the completion status alone cannot express
|
|
2122
|
+
* that difference, because it reports "unavailable" for both.
|
|
2123
|
+
*/
|
|
2124
|
+
verificationPolicyIds() {
|
|
2125
|
+
return this._ensureVerificationTracker().policyIds();
|
|
2126
|
+
}
|
|
1862
2127
|
/** The live verification record with its bounded evidence, if any. */
|
|
1863
2128
|
verificationRecord() {
|
|
1864
2129
|
return this._verificationTracker?.latest();
|