open-multi-agent-kit 0.93.0 → 0.94.0
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 +23 -1
- package/README.md +2 -2
- package/dist/core/agent-session.d.ts +7 -14
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +78 -347
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +11 -1
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/sandbox/default-policy.d.ts +14 -0
- package/dist/core/sandbox/default-policy.d.ts.map +1 -0
- package/dist/core/sandbox/default-policy.js +58 -0
- package/dist/core/sandbox/default-policy.js.map +1 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-bash-runtime.d.ts +62 -0
- package/dist/core/session-bash-runtime.d.ts.map +1 -0
- package/dist/core/session-bash-runtime.js +167 -0
- package/dist/core/session-bash-runtime.js.map +1 -0
- package/dist/core/session-bash-service.d.ts +61 -0
- package/dist/core/session-bash-service.d.ts.map +1 -0
- package/dist/core/session-bash-service.js +155 -0
- package/dist/core/session-bash-service.js.map +1 -0
- package/dist/core/session-compaction-service.d.ts +48 -0
- package/dist/core/session-compaction-service.d.ts.map +1 -0
- package/dist/core/session-compaction-service.js +250 -0
- package/dist/core/session-compaction-service.js.map +1 -0
- package/dist/core/skills-catalog-cache.d.ts +25 -0
- package/dist/core/skills-catalog-cache.d.ts.map +1 -0
- package/dist/core/skills-catalog-cache.js +123 -0
- package/dist/core/skills-catalog-cache.js.map +1 -0
- package/dist/core/skills.d.ts +6 -0
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +25 -4
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +3 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +3 -2
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/diagnostics.d.ts +29 -0
- package/dist/core/tools/diagnostics.d.ts.map +1 -0
- package/dist/core/tools/diagnostics.js +325 -0
- package/dist/core/tools/diagnostics.js.map +1 -0
- package/dist/core/tools/index.d.ts +2 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +18 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/verified-bash-adapter.d.ts +18 -1
- package/dist/core/verified-bash-adapter.d.ts.map +1 -1
- package/dist/core/verified-bash-adapter.js +38 -1
- package/dist/core/verified-bash-adapter.js.map +1 -1
- package/dist/core/verified-bash-runtime.d.ts +13 -0
- package/dist/core/verified-bash-runtime.d.ts.map +1 -0
- package/dist/core/verified-bash-runtime.js +81 -0
- package/dist/core/verified-bash-runtime.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/types/evidence.d.ts +1 -1
- package/dist/types/evidence.d.ts.map +1 -1
- package/dist/types/evidence.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +31 -5
- package/dist/utils/frontmatter.js.map +1 -1
- package/docs/environment-variables.md +4 -0
- package/docs/sdk.md +10 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +15 -15
- package/package.json +5 -5
|
@@ -16,36 +16,31 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
16
16
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { basename, dirname } from "node:path";
|
|
18
18
|
import { isDeepStrictEqual } from "node:util";
|
|
19
|
-
import { repairTranscriptIntegrity, } from "omk-agent-core";
|
|
20
19
|
import { clampThinkingLevel, cleanupSessionResources, getSupportedThinkingLevels, isContextOverflow, modelsAreEqual, resetApiProviders, streamSimple, } from "omk-ai";
|
|
21
20
|
import { theme } from "../modes/interactive/theme/theme.js";
|
|
22
21
|
import { stripFrontmatter } from "../utils/frontmatter.js";
|
|
23
22
|
import { resolvePath } from "../utils/paths.js";
|
|
24
|
-
import {
|
|
23
|
+
import { getShellConfig } from "../utils/shell.js";
|
|
25
24
|
import { sleep } from "../utils/sleep.js";
|
|
26
25
|
import { createAdaptorchBridge } from "./adaptorch-bridge.js";
|
|
27
26
|
import { applyCategoryTimeoutDefaults, resolveAgentToolSettings, resolveToolTimeoutCategory, } from "./agent-tool-settings.js";
|
|
28
27
|
import { formatNoApiKeyFoundMessage, formatNoModelSelectedMessage } from "./auth-guidance.js";
|
|
29
28
|
import { parseBangInvocation } from "./bang-skill-invocation.js";
|
|
30
|
-
import { executeBashWithOperations } from "./bash-executor.js";
|
|
31
|
-
import { classifyShellCommand } from "./command-safety.js";
|
|
32
29
|
import { getCompactionHeadroomThreshold } from "./compaction/compaction.js";
|
|
33
30
|
import { createCompactionHysteresisConfig, createCompactionHysteresisState, stepCompactionHysteresis, } from "./compaction/hysteresis.js";
|
|
34
31
|
import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateProjectedContextTokens, generateBranchSummary, prepareCompaction, resolveCompactionModel, } from "./compaction/index.js";
|
|
35
32
|
import { compactionEmitWillRetry } from "./compaction/resume-policy.js";
|
|
36
|
-
import { createCompactionEnvelope, createCompactionSourceIdentity, createCompactionTransaction, decideCompactionCommit, evaluateCompactionBarrier, validateCompactionEnvelope, } from "./compaction/transaction.js";
|
|
37
33
|
import { estimateToolResultReserve, } from "./context-budget-reserved-tokens.js";
|
|
38
34
|
import { applyContextCacheInvalidation, createContextCacheInvalidationSnapshot, } from "./context-budget-v2-cache-invalidation.js";
|
|
39
35
|
import { createMemoryContextBudgetCacheProviderV2 } from "./context-budget-v2-cache-provider.js";
|
|
40
36
|
import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
|
|
41
37
|
import { exportSessionToHtml } from "./export-html/index.js";
|
|
42
38
|
import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
|
|
43
|
-
import { buildBlockedBashResult, evaluateCommandGate, isCommandSafetyAssumeYesEnabled, } from "./extensions/builtin/command-safety-gate.js";
|
|
44
39
|
import { ExtensionRunner, wrapRegisteredTools, } from "./extensions/index.js";
|
|
45
40
|
import { emitSessionShutdownEvent } from "./extensions/runner.js";
|
|
46
41
|
import { assertTextChatModelForCompletion } from "./grok-harness.js";
|
|
47
42
|
import { grokPlaybookAppendForProvider } from "./grok-playbook.js";
|
|
48
|
-
import {
|
|
43
|
+
import { decideLoadoutAccess } from "./loadout-access-policy.js";
|
|
49
44
|
import { findExactModelReferenceMatch } from "./model-resolver.js";
|
|
50
45
|
import { expandPromptTemplate } from "./prompt-templates.js";
|
|
51
46
|
import { isContentSafetyStopMessage, isStickySafetyModel, isTransientProviderErrorMessage, pickFailoverCandidate, resolveProviderResilience, stickySafetyBlockMessage, } from "./provider-resilience.js";
|
|
@@ -54,8 +49,9 @@ import { classifyTaskV4, resolveThinkingLevelV4WithUncertainty } from "./reasoni
|
|
|
54
49
|
import { redactSensitiveText } from "./redaction.js";
|
|
55
50
|
import { appendRouterFeedbackRecord, } from "./router-feedback-collector.js";
|
|
56
51
|
import { RunJournalStore } from "./run-journal-store.js";
|
|
57
|
-
import {
|
|
58
|
-
import {
|
|
52
|
+
import { SessionBashRuntime } from "./session-bash-runtime.js";
|
|
53
|
+
import { SessionBashService } from "./session-bash-service.js";
|
|
54
|
+
import { SessionCompactionService } from "./session-compaction-service.js";
|
|
59
55
|
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry } from "./session-manager.js";
|
|
60
56
|
import { classifySessionTermination, } from "./session-termination.js";
|
|
61
57
|
import { createSyntheticSourceInfo } from "./source-info.js";
|
|
@@ -63,6 +59,8 @@ import { buildSystemPrompt } from "./system-prompt.js";
|
|
|
63
59
|
import { createLocalBashOperations } from "./tools/bash.js";
|
|
64
60
|
import { createAllToolDefinitions } from "./tools/index.js";
|
|
65
61
|
import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
|
|
62
|
+
import { createVerifiedBashOperations } from "./verified-bash-adapter.js";
|
|
63
|
+
import { resolveSessionWorkspaceScope } from "./verified-bash-runtime.js";
|
|
66
64
|
/**
|
|
67
65
|
* Parse a skill block from message text.
|
|
68
66
|
* Returns null if the text doesn't contain a skill block.
|
|
@@ -120,17 +118,6 @@ function toolNameSetsEqual(left, right) {
|
|
|
120
118
|
return (normalizedLeft.length === normalizedRight.length &&
|
|
121
119
|
normalizedLeft.every((toolName, index) => toolName === normalizedRight[index]));
|
|
122
120
|
}
|
|
123
|
-
function isSandboxDeniedError(error) {
|
|
124
|
-
return error instanceof Error && error.message.startsWith("sandbox: shell denied");
|
|
125
|
-
}
|
|
126
|
-
function buildSandboxDeniedBashResult(reason) {
|
|
127
|
-
return {
|
|
128
|
-
output: reason,
|
|
129
|
-
exitCode: 1,
|
|
130
|
-
cancelled: false,
|
|
131
|
-
truncated: false,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
121
|
function snapshotContractError(reason) {
|
|
135
122
|
return new Error(`Finalized message replacement must be a plain serializable snapshot: ${reason}`);
|
|
136
123
|
}
|
|
@@ -290,10 +277,8 @@ export class AgentSession {
|
|
|
290
277
|
// not re-pick the same candidate (claude-opus-5 → grok → grok → grok loop fix).
|
|
291
278
|
_refusedModels = new Set();
|
|
292
279
|
// Bash execution state
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
_configuredBashSandboxPreflight = undefined;
|
|
296
|
-
_detectedBashSandboxBackend = undefined;
|
|
280
|
+
_bashService;
|
|
281
|
+
_compactionService;
|
|
297
282
|
// Extension system
|
|
298
283
|
_extensionRunner;
|
|
299
284
|
_turnIndex = 0;
|
|
@@ -325,6 +310,7 @@ export class AgentSession {
|
|
|
325
310
|
_replayLedger;
|
|
326
311
|
_replayGoalId;
|
|
327
312
|
_replayLaneId;
|
|
313
|
+
_bashRuntime;
|
|
328
314
|
_transcriptRepair;
|
|
329
315
|
_sessionRiskLevel = "normal";
|
|
330
316
|
_workspaceMutationCount = 0;
|
|
@@ -363,7 +349,6 @@ export class AgentSession {
|
|
|
363
349
|
this._excludedToolNames = config.excludedToolNames ? new Set(config.excludedToolNames) : undefined;
|
|
364
350
|
this._baseToolsOverride = config.baseToolsOverride;
|
|
365
351
|
this._loadoutAccessPolicy = config.loadoutAccessPolicy;
|
|
366
|
-
this._configuredBashSandboxPreflight = config.bashSandboxPreflight;
|
|
367
352
|
this._sessionStartEvent = config.sessionStartEvent ?? { type: "session_start", reason: "startup" };
|
|
368
353
|
this._transcriptRepair = config.transcriptRepair;
|
|
369
354
|
this._replayLedger = config.replayLedger;
|
|
@@ -372,6 +357,37 @@ export class AgentSession {
|
|
|
372
357
|
if (this._replayLedger && this._replayGoalId !== this._replayLedger.getLedger().goalId) {
|
|
373
358
|
throw new Error("AgentSession replayGoalId does not match the replay ledger goal id");
|
|
374
359
|
}
|
|
360
|
+
this._bashRuntime = new SessionBashRuntime({
|
|
361
|
+
cwd: this._cwd,
|
|
362
|
+
getExecutionCwd: () => this.sessionManager.getCwd(),
|
|
363
|
+
getSessionFile: () => this.sessionManager.getSessionFile(),
|
|
364
|
+
replayLedger: this._replayLedger,
|
|
365
|
+
replayGoalId: this._replayGoalId,
|
|
366
|
+
replayLaneId: this._replayLaneId,
|
|
367
|
+
configuredSandboxPreflight: config.bashSandboxPreflight,
|
|
368
|
+
appendReplayEvent: (type, payload) => this._appendReplayEvent(type, payload),
|
|
369
|
+
});
|
|
370
|
+
this._bashService = new SessionBashService({
|
|
371
|
+
settings: this.settingsManager,
|
|
372
|
+
runtime: this._bashRuntime,
|
|
373
|
+
loadoutPolicy: this._loadoutAccessPolicy,
|
|
374
|
+
getCwd: () => this.sessionManager.getCwd(),
|
|
375
|
+
emit: (event) => this._emit(event),
|
|
376
|
+
isStreaming: () => this.isStreaming,
|
|
377
|
+
pushMessage: (message) => this.agent.state.messages.push(message),
|
|
378
|
+
appendMessage: (message) => this.sessionManager.appendMessage(message),
|
|
379
|
+
});
|
|
380
|
+
this._compactionService = new SessionCompactionService({
|
|
381
|
+
sessionManager: this.sessionManager,
|
|
382
|
+
pendingToolCallIds: () => this.agent.state.pendingToolCalls,
|
|
383
|
+
getUserMessageText: (message) => this._getUserMessageText(message),
|
|
384
|
+
cwd: this._cwd,
|
|
385
|
+
invalidateContextBudget: () => this._invalidateContextBudgetCache({ type: "transcriptRepair" }),
|
|
386
|
+
refreshAgentMessages: () => {
|
|
387
|
+
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
388
|
+
},
|
|
389
|
+
recordCommit: () => this._recordCompactionCommitForHysteresis(),
|
|
390
|
+
});
|
|
375
391
|
const sessionFile = this.sessionManager.getSessionFile();
|
|
376
392
|
this._runJournalStore = RunJournalStore.open({
|
|
377
393
|
...(sessionFile ? { journalPath: `${sessionFile}.runjournal` } : {}),
|
|
@@ -465,6 +481,10 @@ export class AgentSession {
|
|
|
465
481
|
});
|
|
466
482
|
this._replayLedger.persist();
|
|
467
483
|
}
|
|
484
|
+
/** Lazy verified-bash executor bound to this session's replay ledger (default-on path). */
|
|
485
|
+
_getVerifiedEvidenceExecutor() {
|
|
486
|
+
return this._bashRuntime.verifiedEvidenceExecutor();
|
|
487
|
+
}
|
|
468
488
|
/** Append one required durable audit record. Persistence failures propagate. */
|
|
469
489
|
_appendRunJournalAudit(event, details) {
|
|
470
490
|
this._runJournalStore.audit({
|
|
@@ -754,12 +774,7 @@ export class AgentSession {
|
|
|
754
774
|
}
|
|
755
775
|
}
|
|
756
776
|
_getBashSandboxPreflight(override) {
|
|
757
|
-
|
|
758
|
-
if (!preflight || preflight.policy.mode === "off" || preflight.backend) {
|
|
759
|
-
return preflight;
|
|
760
|
-
}
|
|
761
|
-
this._detectedBashSandboxBackend ??= detectSandboxBackend();
|
|
762
|
-
return { ...preflight, backend: this._detectedBashSandboxBackend };
|
|
777
|
+
return this._bashRuntime.sandboxPreflight(override);
|
|
763
778
|
}
|
|
764
779
|
async _getRequiredRequestAuth(model) {
|
|
765
780
|
const result = await this._modelRegistry.getApiKeyAndHeaders(model);
|
|
@@ -2361,230 +2376,11 @@ export class AgentSession {
|
|
|
2361
2376
|
// =========================================================================
|
|
2362
2377
|
// Compaction
|
|
2363
2378
|
// =========================================================================
|
|
2364
|
-
_captureCompactionState() {
|
|
2365
|
-
return this.sessionManager.withCompactionCommitLock(() => this._captureCompactionStateLocked());
|
|
2366
|
-
}
|
|
2367
|
-
_captureCompactionStateLocked() {
|
|
2368
|
-
const sessionFile = this.sessionManager.getSessionFile();
|
|
2369
|
-
const bytes = sessionFile && existsSync(sessionFile)
|
|
2370
|
-
? new Uint8Array(readFileSync(sessionFile))
|
|
2371
|
-
: new TextEncoder().encode(`${[this.sessionManager.getHeader(), ...this.sessionManager.getEntries()]
|
|
2372
|
-
.filter((entry) => entry !== null)
|
|
2373
|
-
.map((entry) => JSON.stringify(entry))
|
|
2374
|
-
.join("\n")}\n`);
|
|
2375
|
-
const report = inspectSessionIntegrity(bytes, { activeLeafId: this.sessionManager.getLeafId() });
|
|
2376
|
-
const branchEntries = report.activeBranch;
|
|
2377
|
-
let latestCompactionIndex = -1;
|
|
2378
|
-
for (let index = branchEntries.length - 1; index >= 0; index -= 1) {
|
|
2379
|
-
if (branchEntries[index]?.type === "compaction") {
|
|
2380
|
-
latestCompactionIndex = index;
|
|
2381
|
-
break;
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
const latestCompaction = branchEntries[latestCompactionIndex];
|
|
2385
|
-
const firstKeptIndex = latestCompaction?.type === "compaction"
|
|
2386
|
-
? branchEntries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId)
|
|
2387
|
-
: -1;
|
|
2388
|
-
const sourceEntries = branchEntries.slice(latestCompactionIndex < 0 ? 0 : firstKeptIndex < 0 ? latestCompactionIndex : firstKeptIndex);
|
|
2389
|
-
const firstEntry = sourceEntries[0];
|
|
2390
|
-
const lastEntry = sourceEntries.at(-1);
|
|
2391
|
-
if (!firstEntry || !lastEntry || report.activeLeafId === null) {
|
|
2392
|
-
const barrier = evaluateCompactionBarrier(report, [...this.agent.state.pendingToolCalls]);
|
|
2393
|
-
if (barrier.status !== "ready")
|
|
2394
|
-
throw this._barrierError(barrier);
|
|
2395
|
-
throw new Error("Nothing to compact: the active session branch is empty");
|
|
2396
|
-
}
|
|
2397
|
-
const revision = this.sessionManager.getDurableHeadToken();
|
|
2398
|
-
const source = createCompactionSourceIdentity({
|
|
2399
|
-
sessionId: revision.sessionId,
|
|
2400
|
-
entryIds: sourceEntries.map((entry) => entry.id),
|
|
2401
|
-
firstEntryId: firstEntry.id,
|
|
2402
|
-
lastEntryId: lastEntry.id,
|
|
2403
|
-
sourceSha256: createHash("sha256")
|
|
2404
|
-
.update(sourceEntries.map((entry) => JSON.stringify(entry)).join("\n"), "utf8")
|
|
2405
|
-
.digest("hex"),
|
|
2406
|
-
activeLeafId: report.activeLeafId,
|
|
2407
|
-
messageCount: report.activeMessages.length,
|
|
2408
|
-
});
|
|
2409
|
-
return { report, branchEntries, revision, source };
|
|
2410
|
-
}
|
|
2411
|
-
_captureCompactionProvenance(capture) {
|
|
2412
|
-
let latestIntent = "Continue the current session";
|
|
2413
|
-
for (let index = capture.report.activeMessages.length - 1; index >= 0; index -= 1) {
|
|
2414
|
-
const message = capture.report.activeMessages[index];
|
|
2415
|
-
if (message?.role !== "user")
|
|
2416
|
-
continue;
|
|
2417
|
-
const candidate = sanitizeBinaryOutput(redactSensitiveText(this._getUserMessageText(message)).trim())
|
|
2418
|
-
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, "")
|
|
2419
|
-
.slice(0, 16_384);
|
|
2420
|
-
if (candidate.length > 0)
|
|
2421
|
-
latestIntent = candidate;
|
|
2422
|
-
break;
|
|
2423
|
-
}
|
|
2424
|
-
const modelHistory = capture.branchEntries
|
|
2425
|
-
.flatMap((entry) => {
|
|
2426
|
-
if (entry.type === "model_change") {
|
|
2427
|
-
return [{ entryId: entry.id, provider: entry.provider, modelId: entry.modelId }];
|
|
2428
|
-
}
|
|
2429
|
-
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2430
|
-
return [{ entryId: entry.id, provider: entry.message.provider, modelId: entry.message.model }];
|
|
2431
|
-
}
|
|
2432
|
-
return [];
|
|
2433
|
-
})
|
|
2434
|
-
.slice(-256);
|
|
2435
|
-
const customEntryIds = (customType) => capture.branchEntries
|
|
2436
|
-
.filter((entry) => entry.type === "custom" && entry.customType === customType)
|
|
2437
|
-
.map((entry) => entry.id);
|
|
2438
|
-
return {
|
|
2439
|
-
latestIntent,
|
|
2440
|
-
openTasks: [],
|
|
2441
|
-
laneIds: customEntryIds("lane"),
|
|
2442
|
-
acceptancePredicateIds: customEntryIds("acceptance_predicate"),
|
|
2443
|
-
evidenceReceiptIds: customEntryIds("evidence_receipt"),
|
|
2444
|
-
blockerReasons: [],
|
|
2445
|
-
repairEventIds: [
|
|
2446
|
-
...customEntryIds("transcript_repaired"),
|
|
2447
|
-
...customEntryIds("compaction_transcript_repaired"),
|
|
2448
|
-
],
|
|
2449
|
-
branch: null,
|
|
2450
|
-
worktree: this._cwd,
|
|
2451
|
-
modelHistory,
|
|
2452
|
-
nextAction: latestIntent.slice(0, 4096) || "Continue the current session",
|
|
2453
|
-
};
|
|
2454
|
-
}
|
|
2455
|
-
_barrierError(barrier) {
|
|
2456
|
-
if (barrier.status === "defer") {
|
|
2457
|
-
return new Error(`Compaction deferred until the transcript closes (${barrier.reason})`);
|
|
2458
|
-
}
|
|
2459
|
-
return new Error(`Compaction failed closed on transcript integrity (${barrier.reason}). Run the session doctor before retrying.`);
|
|
2460
|
-
}
|
|
2461
|
-
_evaluateCompactionBarrier(capture, includeMissingTailAsPending, excludedPendingIds = new Set()) {
|
|
2462
|
-
const pending = new Set([...this.agent.state.pendingToolCalls].filter((toolCallId) => !excludedPendingIds.has(toolCallId)));
|
|
2463
|
-
if (includeMissingTailAsPending) {
|
|
2464
|
-
for (const issue of capture.report.transcript?.issues ?? []) {
|
|
2465
|
-
if (issue.kind === "missing_result")
|
|
2466
|
-
pending.add(issue.toolCallId);
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
return evaluateCompactionBarrier(capture.report, [...pending]);
|
|
2470
|
-
}
|
|
2471
|
-
_repairEmergencyCompactionTail(capture) {
|
|
2472
|
-
const barrier = this._evaluateCompactionBarrier(capture, true);
|
|
2473
|
-
if (barrier.status !== "defer" || barrier.reason !== "missing_active_tail_results") {
|
|
2474
|
-
if (barrier.status !== "ready")
|
|
2475
|
-
throw this._barrierError(barrier);
|
|
2476
|
-
return { capture, repairedToolCallIds: new Set() };
|
|
2477
|
-
}
|
|
2478
|
-
const repairedMessages = repairTranscriptIntegrity([...capture.report.activeMessages], "Tool result missing; synthesized to close an emergency compaction barrier");
|
|
2479
|
-
const inserted = repairedMessages.slice(capture.report.activeMessages.length);
|
|
2480
|
-
const repairedToolCallIds = new Set();
|
|
2481
|
-
for (const message of inserted) {
|
|
2482
|
-
if (message.role !== "toolResult") {
|
|
2483
|
-
throw new Error("Emergency compaction repair produced a non-tool result");
|
|
2484
|
-
}
|
|
2485
|
-
const toolResult = message;
|
|
2486
|
-
repairedToolCallIds.add(toolResult.toolCallId);
|
|
2487
|
-
this.sessionManager.appendMessage(toolResult);
|
|
2488
|
-
}
|
|
2489
|
-
this.sessionManager.appendCustomEntry("compaction_transcript_repaired", {
|
|
2490
|
-
insertedToolCallIds: [...repairedToolCallIds],
|
|
2491
|
-
reason: "emergency_compaction",
|
|
2492
|
-
});
|
|
2493
|
-
this._invalidateContextBudgetCache({ type: "transcriptRepair" });
|
|
2494
|
-
const closedCapture = this._captureCompactionState();
|
|
2495
|
-
const closedBarrier = this._evaluateCompactionBarrier(closedCapture, false, repairedToolCallIds);
|
|
2496
|
-
if (closedBarrier.status !== "ready")
|
|
2497
|
-
throw this._barrierError(closedBarrier);
|
|
2498
|
-
this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
|
|
2499
|
-
return { capture: closedCapture, repairedToolCallIds };
|
|
2500
|
-
}
|
|
2501
|
-
_priorCommittedCompactionSourceDigests() {
|
|
2502
|
-
const digests = [];
|
|
2503
|
-
for (const entry of this.sessionManager.getEntries()) {
|
|
2504
|
-
if (entry.type !== "compaction" || typeof entry.details !== "object" || entry.details === null)
|
|
2505
|
-
continue;
|
|
2506
|
-
if (!Object.hasOwn(entry.details, "compactionEnvelope"))
|
|
2507
|
-
continue;
|
|
2508
|
-
const envelope = validateCompactionEnvelope(Reflect.get(entry.details, "compactionEnvelope"));
|
|
2509
|
-
if (envelope.summary !== entry.summary) {
|
|
2510
|
-
throw new Error(`Compaction entry ${entry.id} has invalid provenance. Run the session doctor.`);
|
|
2511
|
-
}
|
|
2512
|
-
digests.push(envelope.source.sourceSha256);
|
|
2513
|
-
}
|
|
2514
|
-
return digests;
|
|
2515
|
-
}
|
|
2516
2379
|
_beginCompactionTransaction(compactionModel, emergency) {
|
|
2517
|
-
|
|
2518
|
-
if (emergency) {
|
|
2519
|
-
capture = this._repairEmergencyCompactionTail(capture).capture;
|
|
2520
|
-
}
|
|
2521
|
-
else {
|
|
2522
|
-
const barrier = this._evaluateCompactionBarrier(capture, false);
|
|
2523
|
-
if (barrier.status !== "ready")
|
|
2524
|
-
throw this._barrierError(barrier);
|
|
2525
|
-
}
|
|
2526
|
-
const transaction = createCompactionTransaction({
|
|
2527
|
-
transactionId: randomUUID(),
|
|
2528
|
-
baseRevision: capture.revision,
|
|
2529
|
-
source: capture.source,
|
|
2530
|
-
createdAt: new Date().toISOString(),
|
|
2531
|
-
model: { provider: compactionModel.provider, id: compactionModel.id },
|
|
2532
|
-
preserved: this._captureCompactionProvenance(capture),
|
|
2533
|
-
});
|
|
2534
|
-
if (this._priorCommittedCompactionSourceDigests().includes(transaction.source.sourceSha256)) {
|
|
2535
|
-
throw new Error("This exact compaction source was already compacted");
|
|
2536
|
-
}
|
|
2537
|
-
return { capture, transaction };
|
|
2538
|
-
}
|
|
2539
|
-
_detailsWithCompactionEnvelope(details, envelope) {
|
|
2540
|
-
if (typeof details === "object" && details !== null && !Array.isArray(details)) {
|
|
2541
|
-
return { ...details, compactionEnvelope: envelope };
|
|
2542
|
-
}
|
|
2543
|
-
return {
|
|
2544
|
-
compactionEnvelope: envelope,
|
|
2545
|
-
...(details === undefined ? {} : { resultDetails: details }),
|
|
2546
|
-
};
|
|
2380
|
+
return this._compactionService.beginTransaction(compactionModel, emergency);
|
|
2547
2381
|
}
|
|
2548
2382
|
_commitCompaction(begun, result, fromExtension) {
|
|
2549
|
-
|
|
2550
|
-
throw new Error("Compaction first-kept entry is outside the captured source");
|
|
2551
|
-
}
|
|
2552
|
-
const committed = this.sessionManager.withCompactionCommitLock(() => {
|
|
2553
|
-
const current = this._captureCompactionState();
|
|
2554
|
-
const barrier = this._evaluateCompactionBarrier(current, false);
|
|
2555
|
-
const decision = decideCompactionCommit({
|
|
2556
|
-
transaction: begun.transaction,
|
|
2557
|
-
currentRevision: current.revision,
|
|
2558
|
-
currentSource: current.source,
|
|
2559
|
-
barrier,
|
|
2560
|
-
priorCommittedSourceDigests: this._priorCommittedCompactionSourceDigests(),
|
|
2561
|
-
});
|
|
2562
|
-
switch (decision.decision) {
|
|
2563
|
-
case "duplicate":
|
|
2564
|
-
throw new Error("This exact compaction source was already compacted");
|
|
2565
|
-
case "stale":
|
|
2566
|
-
throw new Error(`Session changed during compaction (${decision.reason}); generated summary was discarded`);
|
|
2567
|
-
case "defer":
|
|
2568
|
-
case "fail_closed":
|
|
2569
|
-
throw this._barrierError(barrier);
|
|
2570
|
-
case "commit": {
|
|
2571
|
-
const envelope = createCompactionEnvelope({
|
|
2572
|
-
transaction: begun.transaction,
|
|
2573
|
-
decision,
|
|
2574
|
-
summary: result.summary,
|
|
2575
|
-
summarySha256: createHash("sha256").update(result.summary, "utf8").digest("hex"),
|
|
2576
|
-
});
|
|
2577
|
-
const entryId = this.sessionManager.appendCompaction(result.summary, result.firstKeptEntryId, result.tokensBefore, this._detailsWithCompactionEnvelope(result.details, envelope), fromExtension);
|
|
2578
|
-
const entry = this.sessionManager.getEntry(entryId);
|
|
2579
|
-
if (!entry || entry.type !== "compaction") {
|
|
2580
|
-
throw new Error("Compaction commit did not produce a compaction entry");
|
|
2581
|
-
}
|
|
2582
|
-
return { entry, envelope };
|
|
2583
|
-
}
|
|
2584
|
-
}
|
|
2585
|
-
});
|
|
2586
|
-
this._recordCompactionCommitForHysteresis();
|
|
2587
|
-
return committed;
|
|
2383
|
+
return this._compactionService.commit(begun, result, fromExtension);
|
|
2588
2384
|
}
|
|
2589
2385
|
_pendingToolResultReserve(settings) {
|
|
2590
2386
|
const pendingIds = this.agent.state.pendingToolCalls;
|
|
@@ -3432,6 +3228,29 @@ export class AgentSession {
|
|
|
3432
3228
|
commandPrefix: shellCommandPrefix,
|
|
3433
3229
|
shellPath,
|
|
3434
3230
|
...(loadoutAccessGuard ? { loadoutAccessGuard } : {}),
|
|
3231
|
+
...(() => {
|
|
3232
|
+
const sandboxPolicy = this._getBashSandboxPreflight();
|
|
3233
|
+
return sandboxPolicy ? { sandboxPolicy } : {};
|
|
3234
|
+
})(),
|
|
3235
|
+
...(() => {
|
|
3236
|
+
const evidenceExecutor = this._getVerifiedEvidenceExecutor();
|
|
3237
|
+
if (!evidenceExecutor || !this._replayGoalId)
|
|
3238
|
+
return {};
|
|
3239
|
+
const { shell } = getShellConfig(shellPath);
|
|
3240
|
+
const inner = createLocalBashOperations({
|
|
3241
|
+
...(shellPath !== undefined ? { shellPath } : {}),
|
|
3242
|
+
sandboxPolicy: this._getBashSandboxPreflight(),
|
|
3243
|
+
});
|
|
3244
|
+
return {
|
|
3245
|
+
operations: createVerifiedBashOperations(inner, {
|
|
3246
|
+
evidenceExecutor,
|
|
3247
|
+
goalId: this._replayGoalId,
|
|
3248
|
+
...(this._replayLaneId !== undefined ? { laneId: this._replayLaneId } : {}),
|
|
3249
|
+
shell,
|
|
3250
|
+
workspaceScope: resolveSessionWorkspaceScope(this._cwd),
|
|
3251
|
+
}),
|
|
3252
|
+
};
|
|
3253
|
+
})(),
|
|
3435
3254
|
},
|
|
3436
3255
|
edit: loadoutWriteOptions,
|
|
3437
3256
|
write: loadoutWriteOptions,
|
|
@@ -3451,7 +3270,7 @@ export class AgentSession {
|
|
|
3451
3270
|
this._applyExtensionBindings(this._extensionRunner);
|
|
3452
3271
|
const defaultActiveToolNames = this._baseToolsOverride
|
|
3453
3272
|
? Object.keys(this._baseToolsOverride)
|
|
3454
|
-
: ["read", "bash", "edit", "write"];
|
|
3273
|
+
: ["read", "bash", "edit", "write", "diagnostics"];
|
|
3455
3274
|
const baseActiveToolNames = options.activeToolNames ?? defaultActiveToolNames;
|
|
3456
3275
|
this._refreshToolRegistry({
|
|
3457
3276
|
activeToolNames: baseActiveToolNames,
|
|
@@ -3685,123 +3504,35 @@ export class AgentSession {
|
|
|
3685
3504
|
* @param options.sandboxPolicy Trusted sandbox preflight for local bash execution (never sourced from RPC payloads)
|
|
3686
3505
|
*/
|
|
3687
3506
|
async executeBash(command, onChunk, options) {
|
|
3688
|
-
|
|
3689
|
-
const prefix = this.settingsManager.getShellCommandPrefix();
|
|
3690
|
-
const shellPath = this.settingsManager.getShellPath();
|
|
3691
|
-
const resolvedCommand = prefix ? `${prefix}\n${command}` : command;
|
|
3692
|
-
if (this._loadoutAccessPolicy) {
|
|
3693
|
-
assertLoadoutAccess((request) => decideLoadoutAccess(this._loadoutAccessPolicy, request), { operation: "execute", toolName: "bash", command: resolvedCommand });
|
|
3694
|
-
}
|
|
3695
|
-
// Non-negotiable safety floor for headless callers (RPC bash): hard-deny
|
|
3696
|
-
// block-tier commands before any shell is spawned.
|
|
3697
|
-
if (options?.safetyGate === "headless") {
|
|
3698
|
-
const floorVerdict = classifyShellCommand(command);
|
|
3699
|
-
if (floorVerdict.risk === "block") {
|
|
3700
|
-
throw new Error(`OMK §0.1 safety floor blocked bash: [${floorVerdict.rule}] ${floorVerdict.reason}`);
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
// Command-safety parity for non-interactive callers (RPC bash). Interactive
|
|
3704
|
-
// `!`/`!!` bash is gated earlier through the user_bash extension event, which
|
|
3705
|
-
// keeps its prompt-based approval semantics, so it does not pass safetyGate and
|
|
3706
|
-
// is never double-prompted here. confirm/block-tier verdicts deny headlessly;
|
|
3707
|
-
// the EFFECTIVE command (after the shell command prefix) is classified.
|
|
3708
|
-
if (options?.safetyGate === "headless") {
|
|
3709
|
-
const decision = await evaluateCommandGate(resolvedCommand, {
|
|
3710
|
-
hasUI: false,
|
|
3711
|
-
headlessConfirmPolicy: isCommandSafetyAssumeYesEnabled() ? "allow" : "deny",
|
|
3712
|
-
});
|
|
3713
|
-
if (decision?.deny) {
|
|
3714
|
-
const blocked = buildBlockedBashResult(decision.reason);
|
|
3715
|
-
this.recordBashResult(command, blocked, options);
|
|
3716
|
-
return blocked;
|
|
3717
|
-
}
|
|
3718
|
-
}
|
|
3719
|
-
this._bashAbortController = new AbortController();
|
|
3720
|
-
try {
|
|
3721
|
-
try {
|
|
3722
|
-
const result = await executeBashWithOperations(resolvedCommand, this.sessionManager.getCwd(), options?.operations ??
|
|
3723
|
-
createLocalBashOperations({
|
|
3724
|
-
shellPath,
|
|
3725
|
-
sandboxPolicy: this._getBashSandboxPreflight(options?.sandboxPolicy),
|
|
3726
|
-
}), {
|
|
3727
|
-
onChunk: (delta) => {
|
|
3728
|
-
onChunk?.(delta);
|
|
3729
|
-
this._emit({ type: "bash_execution_update", id: options?.id, delta });
|
|
3730
|
-
},
|
|
3731
|
-
signal: this._bashAbortController.signal,
|
|
3732
|
-
});
|
|
3733
|
-
this.recordBashResult(command, result, options);
|
|
3734
|
-
return result;
|
|
3735
|
-
}
|
|
3736
|
-
catch (error) {
|
|
3737
|
-
if (!isSandboxDeniedError(error)) {
|
|
3738
|
-
throw error;
|
|
3739
|
-
}
|
|
3740
|
-
const blocked = buildSandboxDeniedBashResult(error.message);
|
|
3741
|
-
this.recordBashResult(command, blocked, options);
|
|
3742
|
-
return blocked;
|
|
3743
|
-
}
|
|
3744
|
-
}
|
|
3745
|
-
finally {
|
|
3746
|
-
this._bashAbortController = undefined;
|
|
3747
|
-
}
|
|
3507
|
+
return this._bashService.executeBash(command, onChunk, options);
|
|
3748
3508
|
}
|
|
3749
3509
|
/**
|
|
3750
3510
|
* Record a bash execution result in session history.
|
|
3751
3511
|
* Used by executeBash and by extensions that handle bash execution themselves.
|
|
3752
3512
|
*/
|
|
3753
3513
|
recordBashResult(command, result, options) {
|
|
3754
|
-
|
|
3755
|
-
role: "bashExecution",
|
|
3756
|
-
command,
|
|
3757
|
-
output: result.output,
|
|
3758
|
-
exitCode: result.exitCode,
|
|
3759
|
-
cancelled: result.cancelled,
|
|
3760
|
-
truncated: result.truncated,
|
|
3761
|
-
fullOutputPath: result.fullOutputPath,
|
|
3762
|
-
timestamp: Date.now(),
|
|
3763
|
-
excludeFromContext: options?.excludeFromContext,
|
|
3764
|
-
};
|
|
3765
|
-
// If agent is streaming, defer adding to avoid breaking tool_use/tool_result ordering
|
|
3766
|
-
if (this.isStreaming) {
|
|
3767
|
-
// Queue for later - will be flushed on agent_end
|
|
3768
|
-
this._pendingBashMessages.push(bashMessage);
|
|
3769
|
-
}
|
|
3770
|
-
else {
|
|
3771
|
-
// Add to agent state immediately
|
|
3772
|
-
this.agent.state.messages.push(bashMessage);
|
|
3773
|
-
// Save to session
|
|
3774
|
-
this.sessionManager.appendMessage(bashMessage);
|
|
3775
|
-
}
|
|
3514
|
+
this._bashService.recordBashResult(command, result, options);
|
|
3776
3515
|
}
|
|
3777
3516
|
/**
|
|
3778
3517
|
* Cancel running bash command.
|
|
3779
3518
|
*/
|
|
3780
3519
|
abortBash() {
|
|
3781
|
-
this.
|
|
3520
|
+
this._bashService.abortBash();
|
|
3782
3521
|
}
|
|
3783
3522
|
/** Whether a bash command is currently running */
|
|
3784
3523
|
get isBashRunning() {
|
|
3785
|
-
return this.
|
|
3524
|
+
return this._bashService.isBashRunning;
|
|
3786
3525
|
}
|
|
3787
3526
|
/** Whether there are pending bash messages waiting to be flushed */
|
|
3788
3527
|
get hasPendingBashMessages() {
|
|
3789
|
-
return this.
|
|
3528
|
+
return this._bashService.hasPendingBashMessages;
|
|
3790
3529
|
}
|
|
3791
3530
|
/**
|
|
3792
3531
|
* Flush pending bash messages to agent state and session.
|
|
3793
3532
|
* Called after agent turn completes to maintain proper message ordering.
|
|
3794
3533
|
*/
|
|
3795
3534
|
_flushPendingBashMessages() {
|
|
3796
|
-
|
|
3797
|
-
return;
|
|
3798
|
-
for (const bashMessage of this._pendingBashMessages) {
|
|
3799
|
-
// Add to agent state
|
|
3800
|
-
this.agent.state.messages.push(bashMessage);
|
|
3801
|
-
// Save to session
|
|
3802
|
-
this.sessionManager.appendMessage(bashMessage);
|
|
3803
|
-
}
|
|
3804
|
-
this._pendingBashMessages = [];
|
|
3535
|
+
this._bashService.flushPending();
|
|
3805
3536
|
}
|
|
3806
3537
|
// =========================================================================
|
|
3807
3538
|
// Session Management
|