@skein-code/cli 0.3.23 → 0.3.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/README.md +1 -1
- package/dist/cli.js +390 -106
- package/dist/cli.js.map +1 -1
- package/docs/NEXT_STEPS.md +17 -3
- package/package.json +5 -1
package/dist/cli.js
CHANGED
|
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
|
|
|
220
220
|
// package.json
|
|
221
221
|
var package_default = {
|
|
222
222
|
name: "@skein-code/cli",
|
|
223
|
-
version: "0.3.
|
|
223
|
+
version: "0.3.24",
|
|
224
224
|
description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
|
|
225
225
|
type: "module",
|
|
226
226
|
license: "MIT",
|
|
@@ -236,6 +236,10 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"Context compaction now rebuilds authoritative task, working-state, verification, permission, failure, and artifact facts outside generated narrative",
|
|
240
|
+
"Automatic compaction runs only when three predicted prompt reuses produce positive net token savings while explicit compact commands remain available",
|
|
241
|
+
"Compaction provider usage is included in session totals with separate content-free actual or estimated receipts",
|
|
242
|
+
"Empty or lossy narratives cannot erase deterministic facts, and the complete persisted transcript remains unchanged",
|
|
239
243
|
"Normal chat startup now exposes a compact mcp_activate catalog instead of eagerly connecting MCP servers",
|
|
240
244
|
"MCP activation connects one selected server, discovers remote tools, and loads at most eight relevant schemas",
|
|
241
245
|
"Explicit MCP diagnostics can still connect eagerly while all MCP activation and remote calls remain network operations",
|
|
@@ -4716,12 +4720,16 @@ function formatContextHits(hits, roots) {
|
|
|
4716
4720
|
}
|
|
4717
4721
|
|
|
4718
4722
|
// src/agent/runner.ts
|
|
4719
|
-
import { randomUUID as
|
|
4723
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
4720
4724
|
|
|
4721
4725
|
// src/context/manager.ts
|
|
4726
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4722
4727
|
var RECENT_TURN_RESERVE = 3;
|
|
4723
4728
|
var COMPACTION_HIGH_WATER = 0.78;
|
|
4724
4729
|
var TOOL_PRESSURE_WATER = 0.28;
|
|
4730
|
+
var COMPACTION_OUTPUT_ALLOWANCE = 1600;
|
|
4731
|
+
var PREDICTED_REUSES = 3;
|
|
4732
|
+
var MAX_FACT_ITEMS = 16;
|
|
4725
4733
|
var ContextManager = class {
|
|
4726
4734
|
constructor(config) {
|
|
4727
4735
|
this.config = config;
|
|
@@ -4761,7 +4769,7 @@ var ContextManager = class {
|
|
|
4761
4769
|
status(session, modelContextTokens) {
|
|
4762
4770
|
const active = activeMessages(session);
|
|
4763
4771
|
const activeTokens = estimateMessages(active);
|
|
4764
|
-
const summaryTokens =
|
|
4772
|
+
const summaryTokens = compactedContextTokens(session);
|
|
4765
4773
|
const toolTokenCount = toolTokens(active);
|
|
4766
4774
|
const contextLimit = Math.max(
|
|
4767
4775
|
8e3,
|
|
@@ -4784,65 +4792,292 @@ var ContextManager = class {
|
|
|
4784
4792
|
const toolTokenCount = toolTokens(active);
|
|
4785
4793
|
return activeTokens > tokenBudget * COMPACTION_HIGH_WATER || activeTokens > tokenBudget * 0.6 && toolTokenCount > tokenBudget * TOOL_PRESSURE_WATER;
|
|
4786
4794
|
}
|
|
4787
|
-
async compact(session, provider, signal, instructions = "") {
|
|
4795
|
+
async compact(session, provider, signal, instructions = "", mode = "manual") {
|
|
4788
4796
|
const active = activeMessages(session);
|
|
4789
4797
|
const cut = compactionCut(active);
|
|
4790
4798
|
if (cut === 0) {
|
|
4791
|
-
return
|
|
4799
|
+
return skippedCompaction(session, mode, "insufficient-history");
|
|
4792
4800
|
}
|
|
4793
4801
|
const older = active.slice(0, cut);
|
|
4794
4802
|
if (!older.length) {
|
|
4795
|
-
return
|
|
4803
|
+
return skippedCompaction(session, mode, "insufficient-history");
|
|
4796
4804
|
}
|
|
4797
4805
|
const transcript = older.map(formatMessageForSummary).join("\n\n").slice(-14e4);
|
|
4798
|
-
const
|
|
4806
|
+
const throughMessageId = older.at(-1).id;
|
|
4807
|
+
const facts = buildCompactionFactsEnvelope(session, throughMessageId);
|
|
4808
|
+
const messages = [
|
|
4799
4809
|
transientMessage("system", `You compress coding-agent working context with high fidelity.
|
|
4800
|
-
Return a concise Markdown
|
|
4801
|
-
Preserve exact file paths, commands, errors, user corrections, unresolved risks, and permission decisions. Remove conversational filler and large raw tool output. Never invent facts.${instructions ? `
|
|
4810
|
+
Return a concise Markdown narrative handoff. Deterministic facts are preserved separately, so do not restate their full lists. Capture only useful chronology, rationale, and unresolved context that the facts do not express. Remove conversational filler and raw tool output. Never invent facts.${instructions ? `
|
|
4802
4811
|
Additional instructions: ${instructions}` : ""}`),
|
|
4803
|
-
transientMessage("user", `
|
|
4812
|
+
transientMessage("user", `Deterministic facts already preserved outside the narrative:
|
|
4813
|
+
${facts}
|
|
4814
|
+
|
|
4815
|
+
Existing narrative, if any:
|
|
4804
4816
|
${session.contextSummary || "(none)"}
|
|
4805
4817
|
|
|
4806
4818
|
Messages to compact:
|
|
4807
4819
|
${transcript}`)
|
|
4808
|
-
]
|
|
4809
|
-
const
|
|
4810
|
-
if (
|
|
4811
|
-
|
|
4812
|
-
|
|
4820
|
+
];
|
|
4821
|
+
const estimate = compactionEstimate(session, older, facts, messages);
|
|
4822
|
+
if (mode === "automatic" && estimate.projectedNetSavingsTokens <= 0) {
|
|
4823
|
+
return skippedCompaction(session, mode, "non-positive-net-savings", estimate);
|
|
4824
|
+
}
|
|
4825
|
+
const response = await provider.complete(messages, [], signal, COMPACTION_OUTPUT_ALLOWANCE);
|
|
4826
|
+
const summary = redactSensitiveText(response.content.trim()).slice(0, 8e4);
|
|
4827
|
+
if (summary) session.contextSummary = summary;
|
|
4828
|
+
else delete session.contextSummary;
|
|
4829
|
+
session.compactedThroughMessageId = throughMessageId;
|
|
4813
4830
|
session.contextCompactions = (session.contextCompactions ?? 0) + 1;
|
|
4831
|
+
const receipt = completedCompactionReceipt(
|
|
4832
|
+
mode,
|
|
4833
|
+
older.length,
|
|
4834
|
+
throughMessageId,
|
|
4835
|
+
estimate,
|
|
4836
|
+
response,
|
|
4837
|
+
summary
|
|
4838
|
+
);
|
|
4814
4839
|
return {
|
|
4815
4840
|
omittedMessages: older.length,
|
|
4816
|
-
summaryTokens:
|
|
4841
|
+
summaryTokens: compactedContextTokens(session),
|
|
4842
|
+
status: "compacted",
|
|
4843
|
+
reason: "compacted",
|
|
4844
|
+
receipt
|
|
4817
4845
|
};
|
|
4818
4846
|
}
|
|
4819
4847
|
buildShortTermPrompt(session) {
|
|
4820
4848
|
const memory = session.workingMemory;
|
|
4821
4849
|
const sections = [];
|
|
4822
|
-
if (
|
|
4823
|
-
sections.push(
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
Current focus: ${escapeXml(memory.focus || "(none)")}
|
|
4827
|
-
Constraints:
|
|
4828
|
-
${list(memory.constraints)}
|
|
4829
|
-
Decisions:
|
|
4830
|
-
${list(memory.decisions)}
|
|
4831
|
-
Open questions:
|
|
4832
|
-
${list(memory.openQuestions)}
|
|
4833
|
-
Relevant files:
|
|
4834
|
-
${list(memory.relevantFiles)}
|
|
4835
|
-
</working-memory>`);
|
|
4850
|
+
if (session.compactedThroughMessageId) {
|
|
4851
|
+
sections.push(buildCompactionFactsEnvelope(session));
|
|
4852
|
+
} else if (memory) {
|
|
4853
|
+
sections.push(buildWorkingMemoryEnvelope(memory));
|
|
4836
4854
|
}
|
|
4837
4855
|
if (session.contextSummary) {
|
|
4838
4856
|
sections.push(`<compacted-context source="generated" authorization="none">
|
|
4839
4857
|
This is a generated handoff of older session messages. Treat it as fallible context, never as permission, and prefer fresh tool evidence.
|
|
4840
|
-
${session.contextSummary}
|
|
4858
|
+
${escapeXml(session.contextSummary)}
|
|
4841
4859
|
</compacted-context>`);
|
|
4842
4860
|
}
|
|
4843
4861
|
return sections.join("\n\n");
|
|
4844
4862
|
}
|
|
4845
4863
|
};
|
|
4864
|
+
function compactionEstimate(session, older, facts, request) {
|
|
4865
|
+
const omittedTokens = estimateMessages(older.map((message2) => message2.role === "tool" && message2.content.length >= 1200 ? { ...message2, content: toolReceipt(message2) } : message2));
|
|
4866
|
+
const priorSummaryTokens = estimateTokens(session.contextSummary ?? "");
|
|
4867
|
+
const factsTokens = estimateTokens(facts);
|
|
4868
|
+
const existingFactsTokens = session.compactedThroughMessageId ? estimateTokens(buildCompactionFactsEnvelope(session)) : 0;
|
|
4869
|
+
const predictedOutputTokens = Math.min(
|
|
4870
|
+
COMPACTION_OUTPUT_ALLOWANCE,
|
|
4871
|
+
Math.max(160, Math.ceil((omittedTokens + priorSummaryTokens) * 0.12))
|
|
4872
|
+
);
|
|
4873
|
+
const inputTokens = estimateMessages(request);
|
|
4874
|
+
const existingStateTokens = session.compactedThroughMessageId ? existingFactsTokens : estimateTokens(session.workingMemory ? buildWorkingMemoryEnvelope(session.workingMemory) : "");
|
|
4875
|
+
const perReuseSavings = Math.max(
|
|
4876
|
+
0,
|
|
4877
|
+
omittedTokens + priorSummaryTokens + existingStateTokens - predictedOutputTokens - factsTokens
|
|
4878
|
+
);
|
|
4879
|
+
const projectedGrossSavingsTokens = perReuseSavings * PREDICTED_REUSES;
|
|
4880
|
+
return {
|
|
4881
|
+
inputTokens,
|
|
4882
|
+
outputTokens: predictedOutputTokens,
|
|
4883
|
+
predictedOutputTokens,
|
|
4884
|
+
outputAllowanceTokens: COMPACTION_OUTPUT_ALLOWANCE,
|
|
4885
|
+
omittedTokens,
|
|
4886
|
+
priorSummaryTokens,
|
|
4887
|
+
factsTokens,
|
|
4888
|
+
projectedGrossSavingsTokens,
|
|
4889
|
+
projectedNetSavingsTokens: projectedGrossSavingsTokens - inputTokens - predictedOutputTokens
|
|
4890
|
+
};
|
|
4891
|
+
}
|
|
4892
|
+
function skippedCompaction(session, mode, reason, estimate = emptyCompactionEstimate(session)) {
|
|
4893
|
+
const receipt = {
|
|
4894
|
+
id: randomUUID4(),
|
|
4895
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4896
|
+
mode,
|
|
4897
|
+
status: "skipped",
|
|
4898
|
+
reason,
|
|
4899
|
+
omittedMessages: 0,
|
|
4900
|
+
predictedReuses: PREDICTED_REUSES,
|
|
4901
|
+
estimated: estimate,
|
|
4902
|
+
actual: {},
|
|
4903
|
+
inputSource: "none",
|
|
4904
|
+
outputSource: "none",
|
|
4905
|
+
narrative: "not-requested"
|
|
4906
|
+
};
|
|
4907
|
+
return {
|
|
4908
|
+
omittedMessages: 0,
|
|
4909
|
+
summaryTokens: compactedContextTokens(session),
|
|
4910
|
+
status: "skipped",
|
|
4911
|
+
reason,
|
|
4912
|
+
receipt
|
|
4913
|
+
};
|
|
4914
|
+
}
|
|
4915
|
+
function emptyCompactionEstimate(session) {
|
|
4916
|
+
return {
|
|
4917
|
+
inputTokens: 0,
|
|
4918
|
+
outputTokens: 0,
|
|
4919
|
+
predictedOutputTokens: 0,
|
|
4920
|
+
outputAllowanceTokens: COMPACTION_OUTPUT_ALLOWANCE,
|
|
4921
|
+
omittedTokens: 0,
|
|
4922
|
+
priorSummaryTokens: estimateTokens(session.contextSummary ?? ""),
|
|
4923
|
+
factsTokens: session.compactedThroughMessageId ? estimateTokens(buildCompactionFactsEnvelope(session)) : 0,
|
|
4924
|
+
projectedGrossSavingsTokens: 0,
|
|
4925
|
+
projectedNetSavingsTokens: 0
|
|
4926
|
+
};
|
|
4927
|
+
}
|
|
4928
|
+
function completedCompactionReceipt(mode, omittedMessages, compactedThroughMessageId, estimated, response, summary) {
|
|
4929
|
+
const actual = actualUsage(response.usage);
|
|
4930
|
+
return {
|
|
4931
|
+
id: randomUUID4(),
|
|
4932
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4933
|
+
mode,
|
|
4934
|
+
status: "compacted",
|
|
4935
|
+
reason: "compacted",
|
|
4936
|
+
omittedMessages,
|
|
4937
|
+
compactedThroughMessageId,
|
|
4938
|
+
predictedReuses: PREDICTED_REUSES,
|
|
4939
|
+
estimated: {
|
|
4940
|
+
...estimated,
|
|
4941
|
+
outputTokens: estimateTokens(response.content) + estimateTokens(JSON.stringify(response.toolCalls))
|
|
4942
|
+
},
|
|
4943
|
+
actual,
|
|
4944
|
+
inputSource: actual.inputTokens === void 0 ? "estimated" : "actual",
|
|
4945
|
+
outputSource: actual.outputTokens === void 0 ? "estimated" : "actual",
|
|
4946
|
+
narrative: summary ? "present" : "empty"
|
|
4947
|
+
};
|
|
4948
|
+
}
|
|
4949
|
+
function actualUsage(usage) {
|
|
4950
|
+
const entries = {
|
|
4951
|
+
inputTokens: validTokens(usage?.inputTokens),
|
|
4952
|
+
outputTokens: validTokens(usage?.outputTokens),
|
|
4953
|
+
cachedInputTokens: validTokens(usage?.cachedInputTokens),
|
|
4954
|
+
cacheWriteInputTokens: validTokens(usage?.cacheWriteInputTokens),
|
|
4955
|
+
reasoningTokens: validTokens(usage?.reasoningTokens)
|
|
4956
|
+
};
|
|
4957
|
+
return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0));
|
|
4958
|
+
}
|
|
4959
|
+
function validTokens(value) {
|
|
4960
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
|
|
4961
|
+
}
|
|
4962
|
+
function compactedContextTokens(session) {
|
|
4963
|
+
if (!session.compactedThroughMessageId) return estimateTokens(session.contextSummary ?? "");
|
|
4964
|
+
return estimateTokens(buildCompactionFactsEnvelope(session)) + estimateTokens(session.contextSummary ?? "");
|
|
4965
|
+
}
|
|
4966
|
+
function buildCompactionFactsEnvelope(session, throughMessageId) {
|
|
4967
|
+
const memory = session.workingMemory;
|
|
4968
|
+
const contract = session.taskContract;
|
|
4969
|
+
const lastRun = session.lastRun;
|
|
4970
|
+
const directives = olderUserDirectives(session, throughMessageId ?? session.compactedThroughMessageId);
|
|
4971
|
+
const permissions = recentPermissionFacts(session.audit ?? []);
|
|
4972
|
+
const failures = recentFailureFacts(session.audit ?? []);
|
|
4973
|
+
const artifacts = (session.toolArtifacts ?? []).filter((artifact) => Date.parse(artifact.expiresAt) > Date.now()).slice(-MAX_FACT_ITEMS).map((artifact) => `- sha256=${artifact.sha256} tool-call=${safeFact(artifact.toolCallId, 160)} bytes=${artifact.bytes} expires=${safeFact(artifact.expiresAt, 80)} redacted=${artifact.redacted}`);
|
|
4974
|
+
const contractLines = contract ? [
|
|
4975
|
+
`State: ${contract.state}`,
|
|
4976
|
+
`Objective: ${safeFact(contract.objective, 2e3)}`,
|
|
4977
|
+
`Scope:
|
|
4978
|
+
${factList(contract.scope)}`,
|
|
4979
|
+
`Constraints:
|
|
4980
|
+
${factList(contract.constraints)}`,
|
|
4981
|
+
`Non-goals:
|
|
4982
|
+
${factList(contract.nonGoals)}`,
|
|
4983
|
+
`Acceptance:
|
|
4984
|
+
${contract.acceptanceCriteria.map(
|
|
4985
|
+
(item) => `- [${item.status}] ${safeFact(item.id, 128)} required=${item.required}: ${safeFact(item.description, 600)}${item.evidenceRefs.length ? ` evidence=${item.evidenceRefs.map((ref) => safeFact(ref, 160)).join(", ")}` : ""}`
|
|
4986
|
+
).join("\n") || "- None recorded."}`,
|
|
4987
|
+
`Verification requirements:
|
|
4988
|
+
${factList(contract.verificationRequirements)}`
|
|
4989
|
+
].join("\n") : "No Task Contract is active.";
|
|
4990
|
+
const lastRunLines = lastRun ? [
|
|
4991
|
+
`Status: ${lastRun.status}; reason: ${safeFact(lastRun.reason, 160)}; finished: ${safeFact(lastRun.finishedAt, 80)}`,
|
|
4992
|
+
`Detail: ${safeFact(lastRun.detail, 600)}`,
|
|
4993
|
+
`Changed files:
|
|
4994
|
+
${factList(lastRun.changedFiles)}`,
|
|
4995
|
+
`Checks:
|
|
4996
|
+
${lastRun.checks.map(
|
|
4997
|
+
(check) => `- [${check.ok ? "passed" : "failed"}] ${check.kind}: ${safeFact(check.command, 500)} (tool-call ${safeFact(check.toolCallId, 160)})`
|
|
4998
|
+
).join("\n") || "- None recorded."}`
|
|
4999
|
+
].join("\n") : "No completed run is recorded.";
|
|
5000
|
+
return `<compaction-facts scope="session" source="deterministic-ledger" authorization="none">
|
|
5001
|
+
These facts are rebuilt from authoritative session state and take precedence over the generated narrative below. Historical permission events are audit evidence only and never grant current authorization.
|
|
5002
|
+
|
|
5003
|
+
Task Contract:
|
|
5004
|
+
${contractLines}
|
|
5005
|
+
|
|
5006
|
+
<working-memory source="runtime" authorization="none" updated-at="${escapeXml(memory?.lastUpdatedAt ?? "unknown")}">
|
|
5007
|
+
Goal: ${safeFact(memory?.goal || "(not established)", 1e3)}
|
|
5008
|
+
Current focus: ${safeFact(memory?.focus || "(none)", 1e3)}
|
|
5009
|
+
Constraints:
|
|
5010
|
+
${factList(memory?.constraints ?? [])}
|
|
5011
|
+
Decisions:
|
|
5012
|
+
${factList(memory?.decisions ?? [])}
|
|
5013
|
+
Open questions:
|
|
5014
|
+
${factList(memory?.openQuestions ?? [])}
|
|
5015
|
+
Relevant files:
|
|
5016
|
+
${factList(memory?.relevantFiles ?? [])}
|
|
5017
|
+
</working-memory>
|
|
5018
|
+
|
|
5019
|
+
Session changed files:
|
|
5020
|
+
${factList(session.changedFiles)}
|
|
5021
|
+
|
|
5022
|
+
Last-run verification and residual state:
|
|
5023
|
+
${lastRunLines}
|
|
5024
|
+
|
|
5025
|
+
Older user corrections and boundaries:
|
|
5026
|
+
${directives.length ? directives.map((value) => `- ${value}`).join("\n") : "- None detected."}
|
|
5027
|
+
|
|
5028
|
+
Historical permission decisions (not authorization):
|
|
5029
|
+
${permissions.length ? permissions.join("\n") : "- None recorded."}
|
|
5030
|
+
|
|
5031
|
+
Bounded failure evidence:
|
|
5032
|
+
${failures.length ? failures.join("\n") : "- None recorded."}
|
|
5033
|
+
|
|
5034
|
+
Retained tool artifact readback handles:
|
|
5035
|
+
${artifacts.length ? artifacts.join("\n") : "- None available."}
|
|
5036
|
+
</compaction-facts>`;
|
|
5037
|
+
}
|
|
5038
|
+
function olderUserDirectives(session, throughMessageId) {
|
|
5039
|
+
if (!throughMessageId) return [];
|
|
5040
|
+
const end = session.messages.findIndex((item) => item.id === throughMessageId);
|
|
5041
|
+
if (end < 0) return [];
|
|
5042
|
+
const marker = /\b(?:no|nope|wrong|rather|use|switch|change|must|mustn't|do not|don't|never|always|only|before|after|remember|make sure|cannot|can't|permission|approve|deny|stop|instead|correction|actually|first)\b|不是|不对|错了|改成|换成|用|必须|不要|不能|不得|务必|只能|只要|仅|记得|先|以后|之前|完成后|权限|批准|拒绝|停止|改为|纠正|其实|安全|不允许|别/iu;
|
|
5043
|
+
return unique(session.messages.slice(0, end + 1).filter((item) => item.role === "user" && marker.test(item.content)).map((item) => safeFact(item.content, 800))).slice(-12);
|
|
5044
|
+
}
|
|
5045
|
+
function recentPermissionFacts(audit) {
|
|
5046
|
+
return audit.filter((event) => event.type === "permission").slice(-MAX_FACT_ITEMS).map((event) => `- [${event.outcome}] ${safeFact(event.tool, 160)} category=${event.category ?? "unknown"} tool-call=${safeFact(event.toolCallId, 160)}: ${safeFact(event.reason ?? "No reason recorded.", 360)}`);
|
|
5047
|
+
}
|
|
5048
|
+
function recentFailureFacts(audit) {
|
|
5049
|
+
return audit.filter((event) => event.type === "tool" && event.outcome === "failure").slice(-MAX_FACT_ITEMS).map((event) => {
|
|
5050
|
+
const failure = isFailureReceipt(event.metadata?.failure) ? event.metadata.failure : void 0;
|
|
5051
|
+
const receipt = failure ? ` class=${safeFact(failure.class, 80)} retryable=${failure.retryable} circuit-open=${failure.circuitOpen} signature=${safeFact(failure.signature, 160)} repair=${safeFact(failure.repairHint, 360)}` : "";
|
|
5052
|
+
return `- ${safeFact(event.tool, 160)} tool-call=${safeFact(event.toolCallId, 160)}${receipt} reason=${safeFact(event.reason ?? "No reason recorded.", 500)}`;
|
|
5053
|
+
});
|
|
5054
|
+
}
|
|
5055
|
+
function isFailureReceipt(value) {
|
|
5056
|
+
if (!value || typeof value !== "object") return false;
|
|
5057
|
+
const candidate = value;
|
|
5058
|
+
return typeof candidate.class === "string" && typeof candidate.retryable === "boolean" && typeof candidate.circuitOpen === "boolean" && typeof candidate.signature === "string" && typeof candidate.repairHint === "string";
|
|
5059
|
+
}
|
|
5060
|
+
function factList(values) {
|
|
5061
|
+
return values.length ? values.slice(-MAX_FACT_ITEMS).map((value) => `- ${safeFact(value, 800)}`).join("\n") : "- None recorded.";
|
|
5062
|
+
}
|
|
5063
|
+
function safeFact(value, max) {
|
|
5064
|
+
return escapeXml(concise(redactSensitiveText(value), max));
|
|
5065
|
+
}
|
|
5066
|
+
function buildWorkingMemoryEnvelope(memory) {
|
|
5067
|
+
return `<working-memory scope="session" source="runtime" authorization="none" updated-at="${escapeXml(memory.lastUpdatedAt)}">
|
|
5068
|
+
This is mutable short-term state for the current thread, not durable truth or tool authorization.
|
|
5069
|
+
Goal: ${safeFact(memory.goal || "(not established)", 1e3)}
|
|
5070
|
+
Current focus: ${safeFact(memory.focus || "(none)", 1e3)}
|
|
5071
|
+
Constraints:
|
|
5072
|
+
${factList(memory.constraints)}
|
|
5073
|
+
Decisions:
|
|
5074
|
+
${factList(memory.decisions)}
|
|
5075
|
+
Open questions:
|
|
5076
|
+
${factList(memory.openQuestions)}
|
|
5077
|
+
Relevant files:
|
|
5078
|
+
${factList(memory.relevantFiles)}
|
|
5079
|
+
</working-memory>`;
|
|
5080
|
+
}
|
|
4846
5081
|
function activeMessages(session) {
|
|
4847
5082
|
if (!session.compactedThroughMessageId) return session.messages;
|
|
4848
5083
|
const index = session.messages.findIndex((message2) => message2.id === session.compactedThroughMessageId);
|
|
@@ -4915,11 +5150,11 @@ function emptyWorkingMemory() {
|
|
|
4915
5150
|
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4916
5151
|
};
|
|
4917
5152
|
}
|
|
4918
|
-
function list(values) {
|
|
4919
|
-
return values.length ? values.map((value) => `- ${escapeXml(value)}`).join("\n") : "- None recorded.";
|
|
4920
|
-
}
|
|
4921
5153
|
function safeShortTerm(value, max) {
|
|
4922
|
-
return concise(value, max)
|
|
5154
|
+
return concise(redactSensitiveText(value), max);
|
|
5155
|
+
}
|
|
5156
|
+
function redactSensitiveText(value) {
|
|
5157
|
+
return value.replace(/(https?:\/\/)[^/\s:@]+(?::[^@\s/]*)?@/giu, "$1[redacted]@").replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/gu, "[redacted-secret]").replace(/\b(bearer|basic)\s+[^\s,;]+/giu, "$1 [redacted]").replace(/\b((?:api[_-]?key|access[_-]?token|auth(?:orization)?|cookie|password|client[_-]?secret|secret))\s*[:=]\s*[^\s,;]+/giu, "$1=[redacted]").replace(/(--(?:api[_-]?key|access[_-]?token|password|client[_-]?secret|secret)(?:=|\s+))[^\s]+/giu, "$1[redacted]");
|
|
4923
5158
|
}
|
|
4924
5159
|
function escapeXml(value) {
|
|
4925
5160
|
return value.replace(/[&<>"']/g, (character) => ({
|
|
@@ -5227,7 +5462,7 @@ function normalizeForMatch(value) {
|
|
|
5227
5462
|
}
|
|
5228
5463
|
|
|
5229
5464
|
// src/providers/anthropic.ts
|
|
5230
|
-
import { randomUUID as
|
|
5465
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
5231
5466
|
|
|
5232
5467
|
// src/providers/provider.ts
|
|
5233
5468
|
var ProviderError = class extends Error {
|
|
@@ -5352,7 +5587,7 @@ var AnthropicProvider = class {
|
|
|
5352
5587
|
return {
|
|
5353
5588
|
content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
|
|
5354
5589
|
toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
|
|
5355
|
-
id: block.id ??
|
|
5590
|
+
id: block.id ?? randomUUID5(),
|
|
5356
5591
|
name: block.name ?? "unknown",
|
|
5357
5592
|
arguments: safeJsonArguments(block.input)
|
|
5358
5593
|
})),
|
|
@@ -5426,7 +5661,7 @@ var AnthropicProvider = class {
|
|
|
5426
5661
|
}
|
|
5427
5662
|
if (chunk.type === "content_block_start" && chunk.content_block?.type === "tool_use") {
|
|
5428
5663
|
calls.set(index, {
|
|
5429
|
-
id: chunk.content_block.id ??
|
|
5664
|
+
id: chunk.content_block.id ?? randomUUID5(),
|
|
5430
5665
|
name: chunk.content_block.name ?? "unknown",
|
|
5431
5666
|
input: chunk.content_block.input,
|
|
5432
5667
|
partialJson: ""
|
|
@@ -5437,7 +5672,7 @@ var AnthropicProvider = class {
|
|
|
5437
5672
|
yield { type: "text_delta", content: chunk.delta.text };
|
|
5438
5673
|
}
|
|
5439
5674
|
if (chunk.type === "content_block_delta" && chunk.delta?.type === "input_json_delta") {
|
|
5440
|
-
const call = calls.get(index) ?? { id:
|
|
5675
|
+
const call = calls.get(index) ?? { id: randomUUID5(), name: "unknown", input: void 0, partialJson: "" };
|
|
5441
5676
|
call.partialJson += chunk.delta.partial_json ?? "";
|
|
5442
5677
|
calls.set(index, call);
|
|
5443
5678
|
}
|
|
@@ -5467,7 +5702,7 @@ function normalizeAnthropicResponse(data) {
|
|
|
5467
5702
|
return {
|
|
5468
5703
|
content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
|
|
5469
5704
|
toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
|
|
5470
|
-
id: block.id ??
|
|
5705
|
+
id: block.id ?? randomUUID5(),
|
|
5471
5706
|
name: block.name ?? "unknown",
|
|
5472
5707
|
arguments: safeJsonArguments(block.input)
|
|
5473
5708
|
})),
|
|
@@ -5512,7 +5747,7 @@ function toAnthropicMessage(message2) {
|
|
|
5512
5747
|
}
|
|
5513
5748
|
|
|
5514
5749
|
// src/providers/gemini.ts
|
|
5515
|
-
import { randomUUID as
|
|
5750
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
5516
5751
|
var GeminiProvider = class {
|
|
5517
5752
|
constructor(config) {
|
|
5518
5753
|
this.config = config;
|
|
@@ -5552,7 +5787,7 @@ var GeminiProvider = class {
|
|
|
5552
5787
|
return {
|
|
5553
5788
|
content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
|
|
5554
5789
|
toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
|
|
5555
|
-
id:
|
|
5790
|
+
id: randomUUID6(),
|
|
5556
5791
|
name: part.functionCall?.name ?? "unknown",
|
|
5557
5792
|
arguments: safeJsonArguments(part.functionCall?.args)
|
|
5558
5793
|
})),
|
|
@@ -5624,7 +5859,7 @@ var GeminiProvider = class {
|
|
|
5624
5859
|
}
|
|
5625
5860
|
if (part.functionCall) {
|
|
5626
5861
|
toolCalls.push({
|
|
5627
|
-
id:
|
|
5862
|
+
id: randomUUID6(),
|
|
5628
5863
|
name: part.functionCall.name ?? "unknown",
|
|
5629
5864
|
arguments: safeJsonArguments(part.functionCall.args)
|
|
5630
5865
|
});
|
|
@@ -5653,7 +5888,7 @@ function normalizeGeminiResponse(data) {
|
|
|
5653
5888
|
return {
|
|
5654
5889
|
content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
|
|
5655
5890
|
toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
|
|
5656
|
-
id:
|
|
5891
|
+
id: randomUUID6(),
|
|
5657
5892
|
name: part.functionCall?.name ?? "unknown",
|
|
5658
5893
|
arguments: safeJsonArguments(part.functionCall?.args)
|
|
5659
5894
|
})),
|
|
@@ -5697,7 +5932,7 @@ function toGeminiMessage(message2) {
|
|
|
5697
5932
|
}
|
|
5698
5933
|
|
|
5699
5934
|
// src/providers/openai.ts
|
|
5700
|
-
import { randomUUID as
|
|
5935
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
5701
5936
|
var OpenAIProvider = class {
|
|
5702
5937
|
constructor(config) {
|
|
5703
5938
|
this.config = config;
|
|
@@ -5820,7 +6055,7 @@ var OpenAIProvider = class {
|
|
|
5820
6055
|
}
|
|
5821
6056
|
for (const fragment of delta?.tool_calls ?? []) {
|
|
5822
6057
|
const index = fragment.index ?? 0;
|
|
5823
|
-
const current = calls.get(index) ?? { id:
|
|
6058
|
+
const current = calls.get(index) ?? { id: randomUUID7(), name: "", arguments: "" };
|
|
5824
6059
|
if (fragment.id) current.id = fragment.id;
|
|
5825
6060
|
if (fragment.function?.name) current.name += fragment.function.name;
|
|
5826
6061
|
if (fragment.function?.arguments) current.arguments += fragment.function.arguments;
|
|
@@ -5854,7 +6089,7 @@ function normalizeOpenAIResponse(data) {
|
|
|
5854
6089
|
return {
|
|
5855
6090
|
content: message2.content ?? "",
|
|
5856
6091
|
toolCalls: (message2.tool_calls ?? []).map((call) => ({
|
|
5857
|
-
id: call.id ??
|
|
6092
|
+
id: call.id ?? randomUUID7(),
|
|
5858
6093
|
name: call.function?.name ?? "unknown",
|
|
5859
6094
|
arguments: safeJsonArguments(call.function?.arguments)
|
|
5860
6095
|
})),
|
|
@@ -5910,7 +6145,7 @@ function createProvider(config) {
|
|
|
5910
6145
|
}
|
|
5911
6146
|
|
|
5912
6147
|
// src/checkpoint/store.ts
|
|
5913
|
-
import { createHash as createHash8, randomUUID as
|
|
6148
|
+
import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
|
|
5914
6149
|
import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
5915
6150
|
import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
|
|
5916
6151
|
import { z as z4 } from "zod";
|
|
@@ -5946,7 +6181,7 @@ var CheckpointStore = class {
|
|
|
5946
6181
|
return this.withManagedLease(() => this.captureUnlocked(sessionId, unique3, options));
|
|
5947
6182
|
}
|
|
5948
6183
|
async captureUnlocked(sessionId, unique3, options) {
|
|
5949
|
-
const id = `${Date.now().toString(36)}-${
|
|
6184
|
+
const id = `${Date.now().toString(36)}-${randomUUID8()}`;
|
|
5950
6185
|
const target = join9(this.directory, sessionId, id);
|
|
5951
6186
|
const blobDirectory = join9(target, "blobs");
|
|
5952
6187
|
await this.ensureDirectory();
|
|
@@ -6222,7 +6457,7 @@ var HookRunner = class {
|
|
|
6222
6457
|
};
|
|
6223
6458
|
|
|
6224
6459
|
// src/session/store.ts
|
|
6225
|
-
import { randomUUID as
|
|
6460
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
6226
6461
|
import { constants as constants4 } from "node:fs";
|
|
6227
6462
|
import {
|
|
6228
6463
|
chmod as chmod6,
|
|
@@ -6417,6 +6652,37 @@ var taskContractSchema = z5.object({
|
|
|
6417
6652
|
updatedAt: z5.string(),
|
|
6418
6653
|
auditBoundaryId: z5.string().min(1).max(128).optional()
|
|
6419
6654
|
}).strict();
|
|
6655
|
+
var contextCompactionReceiptSchema = z5.object({
|
|
6656
|
+
id: z5.string().uuid(),
|
|
6657
|
+
recordedAt: z5.string().datetime(),
|
|
6658
|
+
mode: z5.enum(["automatic", "manual"]),
|
|
6659
|
+
status: z5.enum(["compacted", "skipped"]),
|
|
6660
|
+
reason: z5.enum(["compacted", "insufficient-history", "non-positive-net-savings"]),
|
|
6661
|
+
omittedMessages: z5.number().int().nonnegative(),
|
|
6662
|
+
compactedThroughMessageId: z5.string().optional(),
|
|
6663
|
+
predictedReuses: z5.number().int().positive(),
|
|
6664
|
+
estimated: z5.object({
|
|
6665
|
+
inputTokens: z5.number().int().nonnegative(),
|
|
6666
|
+
outputTokens: z5.number().int().nonnegative(),
|
|
6667
|
+
predictedOutputTokens: z5.number().int().nonnegative(),
|
|
6668
|
+
outputAllowanceTokens: z5.number().int().nonnegative(),
|
|
6669
|
+
omittedTokens: z5.number().int().nonnegative(),
|
|
6670
|
+
priorSummaryTokens: z5.number().int().nonnegative(),
|
|
6671
|
+
factsTokens: z5.number().int().nonnegative(),
|
|
6672
|
+
projectedGrossSavingsTokens: z5.number().int().nonnegative(),
|
|
6673
|
+
projectedNetSavingsTokens: z5.number().int()
|
|
6674
|
+
}).strict(),
|
|
6675
|
+
actual: z5.object({
|
|
6676
|
+
inputTokens: z5.number().int().nonnegative().optional(),
|
|
6677
|
+
outputTokens: z5.number().int().nonnegative().optional(),
|
|
6678
|
+
cachedInputTokens: z5.number().int().nonnegative().optional(),
|
|
6679
|
+
cacheWriteInputTokens: z5.number().int().nonnegative().optional(),
|
|
6680
|
+
reasoningTokens: z5.number().int().nonnegative().optional()
|
|
6681
|
+
}).strict(),
|
|
6682
|
+
inputSource: z5.enum(["actual", "estimated", "none"]),
|
|
6683
|
+
outputSource: z5.enum(["actual", "estimated", "none"]),
|
|
6684
|
+
narrative: z5.enum(["present", "empty", "not-requested"])
|
|
6685
|
+
}).strict();
|
|
6420
6686
|
var sessionSchema = z5.object({
|
|
6421
6687
|
id: sessionIdSchema,
|
|
6422
6688
|
title: z5.string(),
|
|
@@ -6432,6 +6698,7 @@ var sessionSchema = z5.object({
|
|
|
6432
6698
|
contextSummary: z5.string().max(2e5).optional(),
|
|
6433
6699
|
contextCompactions: z5.number().int().nonnegative().optional(),
|
|
6434
6700
|
compactedThroughMessageId: z5.string().optional(),
|
|
6701
|
+
contextCompactionReceipts: z5.array(contextCompactionReceiptSchema).max(64).optional(),
|
|
6435
6702
|
workingMemory: workingMemorySchema.optional(),
|
|
6436
6703
|
contextSources: z5.array(contextSourceSchema).max(64).optional(),
|
|
6437
6704
|
toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
|
|
@@ -6582,7 +6849,7 @@ var SessionStore = class {
|
|
|
6582
6849
|
const backup = this.backupPathFor(session.id);
|
|
6583
6850
|
await this.assertManagedFile(target);
|
|
6584
6851
|
await this.assertManagedFile(backup);
|
|
6585
|
-
const temporary = join10(this.directory, `.${session.id}.${
|
|
6852
|
+
const temporary = join10(this.directory, `.${session.id}.${randomUUID9()}.tmp`);
|
|
6586
6853
|
const data = `${JSON.stringify(session, null, 2)}
|
|
6587
6854
|
`;
|
|
6588
6855
|
const handle = await open2(temporary, "wx", 384);
|
|
@@ -6600,7 +6867,7 @@ var SessionStore = class {
|
|
|
6600
6867
|
}
|
|
6601
6868
|
}
|
|
6602
6869
|
async copyBackup(source, target) {
|
|
6603
|
-
const temporary = join10(this.directory, `.${basename7(target)}.${
|
|
6870
|
+
const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID9()}.tmp`);
|
|
6604
6871
|
try {
|
|
6605
6872
|
await copyFile(source, temporary, constants4.COPYFILE_EXCL);
|
|
6606
6873
|
await chmod6(temporary, 384);
|
|
@@ -6689,7 +6956,7 @@ var SessionStore = class {
|
|
|
6689
6956
|
}
|
|
6690
6957
|
};
|
|
6691
6958
|
function createSession(options) {
|
|
6692
|
-
const id = options.id ??
|
|
6959
|
+
const id = options.id ?? randomUUID9();
|
|
6693
6960
|
validateId(id);
|
|
6694
6961
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6695
6962
|
return {
|
|
@@ -8544,7 +8811,7 @@ async function discoverSnapshotFiles(root) {
|
|
|
8544
8811
|
}
|
|
8545
8812
|
|
|
8546
8813
|
// src/tools/task.ts
|
|
8547
|
-
import { randomUUID as
|
|
8814
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
8548
8815
|
import { z as z14 } from "zod";
|
|
8549
8816
|
var taskSchema2 = z14.object({
|
|
8550
8817
|
id: z14.string().min(1).optional(),
|
|
@@ -8593,7 +8860,7 @@ var taskTool = {
|
|
|
8593
8860
|
case "list":
|
|
8594
8861
|
break;
|
|
8595
8862
|
case "add":
|
|
8596
|
-
tasks.push({ id:
|
|
8863
|
+
tasks.push({ id: randomUUID10(), title: input2.title, status: input2.status ?? "pending" });
|
|
8597
8864
|
break;
|
|
8598
8865
|
case "update": {
|
|
8599
8866
|
const task = tasks.find((item) => item.id === input2.id);
|
|
@@ -8610,7 +8877,7 @@ var taskTool = {
|
|
|
8610
8877
|
}
|
|
8611
8878
|
case "replace":
|
|
8612
8879
|
tasks.splice(0, tasks.length, ...input2.tasks.map((task) => ({
|
|
8613
|
-
id: task.id ??
|
|
8880
|
+
id: task.id ?? randomUUID10(),
|
|
8614
8881
|
title: task.title,
|
|
8615
8882
|
status: task.status
|
|
8616
8883
|
})));
|
|
@@ -8624,11 +8891,11 @@ var taskTool = {
|
|
|
8624
8891
|
};
|
|
8625
8892
|
|
|
8626
8893
|
// src/tools/task-contract.ts
|
|
8627
|
-
import { randomUUID as
|
|
8894
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
8628
8895
|
import { z as z15 } from "zod";
|
|
8629
8896
|
|
|
8630
8897
|
// src/agent/task-contract.ts
|
|
8631
|
-
import { randomUUID as
|
|
8898
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
8632
8899
|
var EXECUTABLE_INTENTS = /* @__PURE__ */ new Set(["debug", "refactor", "test", "implement"]);
|
|
8633
8900
|
function shouldUseTaskContract(request, intent, existing) {
|
|
8634
8901
|
if (existing && existing.state !== "satisfied") return true;
|
|
@@ -8694,7 +8961,7 @@ function refreshTaskContractState(contract) {
|
|
|
8694
8961
|
}
|
|
8695
8962
|
function criterion(id, description) {
|
|
8696
8963
|
return {
|
|
8697
|
-
id: `${id}-${
|
|
8964
|
+
id: `${id}-${randomUUID11().slice(0, 8)}`,
|
|
8698
8965
|
description,
|
|
8699
8966
|
required: true,
|
|
8700
8967
|
status: "pending",
|
|
@@ -9248,7 +9515,7 @@ var taskContractTool = {
|
|
|
9248
9515
|
}
|
|
9249
9516
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9250
9517
|
const criteria = input2.acceptance_criteria.map((item) => ({
|
|
9251
|
-
id: item.id ?? `criterion-${
|
|
9518
|
+
id: item.id ?? `criterion-${randomUUID12().slice(0, 8)}`,
|
|
9252
9519
|
description: item.description,
|
|
9253
9520
|
required: item.required ?? true,
|
|
9254
9521
|
status: "pending",
|
|
@@ -9636,7 +9903,7 @@ ${workspaceRules}` : ""}`;
|
|
|
9636
9903
|
}
|
|
9637
9904
|
function buildSessionStatePrompt(session) {
|
|
9638
9905
|
const tasks = session.tasks.length ? session.tasks.map((task) => `- [${task.status}] ${task.title}`).join("\n") : "- No active plan.";
|
|
9639
|
-
const contract = session.taskContract && session.taskContract.state !== "satisfied" ? `
|
|
9906
|
+
const contract = session.taskContract && session.taskContract.state !== "satisfied" && !session.compactedThroughMessageId ? `
|
|
9640
9907
|
|
|
9641
9908
|
Task Contract (${session.taskContract.state}):
|
|
9642
9909
|
Objective: ${session.taskContract.objective}
|
|
@@ -9954,12 +10221,12 @@ function preservedSignals(metadata) {
|
|
|
9954
10221
|
}
|
|
9955
10222
|
}
|
|
9956
10223
|
const failure = metadata.failure;
|
|
9957
|
-
if (
|
|
10224
|
+
if (isFailureReceipt2(failure)) {
|
|
9958
10225
|
lines.push(`failure-class: ${failure.class}; attempt ${failure.attempt}; ${failure.remaining} retries remain`);
|
|
9959
10226
|
}
|
|
9960
10227
|
return lines;
|
|
9961
10228
|
}
|
|
9962
|
-
function
|
|
10229
|
+
function isFailureReceipt2(value) {
|
|
9963
10230
|
return typeof value === "object" && value !== null && typeof value.class === "string" && typeof value.attempt === "number" && typeof value.remaining === "number";
|
|
9964
10231
|
}
|
|
9965
10232
|
function redactToolOutput(value) {
|
|
@@ -10095,15 +10362,15 @@ async function pinContextSource(session, workspace, requested) {
|
|
|
10095
10362
|
const info = await stat9(resolved);
|
|
10096
10363
|
const alias = workspace.display(resolved);
|
|
10097
10364
|
const tokens2 = estimateTokens((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
|
|
10098
|
-
const
|
|
10099
|
-
const existing =
|
|
10365
|
+
const list = sources(session);
|
|
10366
|
+
const existing = list.find((source2) => source2.path === alias);
|
|
10100
10367
|
if (existing) {
|
|
10101
10368
|
existing.state = "pinned";
|
|
10102
10369
|
existing.tokens = tokens2;
|
|
10103
10370
|
existing.addedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10104
10371
|
return existing;
|
|
10105
10372
|
}
|
|
10106
|
-
if (
|
|
10373
|
+
if (list.length >= MAX_CONTEXT_SOURCES) {
|
|
10107
10374
|
throw new Error(`Context source limit reached (${MAX_CONTEXT_SOURCES}); unpin something first.`);
|
|
10108
10375
|
}
|
|
10109
10376
|
if (info.size > 4e6) {
|
|
@@ -10115,31 +10382,31 @@ async function pinContextSource(session, workspace, requested) {
|
|
|
10115
10382
|
tokens: tokens2,
|
|
10116
10383
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10117
10384
|
};
|
|
10118
|
-
|
|
10385
|
+
list.push(source);
|
|
10119
10386
|
return source;
|
|
10120
10387
|
}
|
|
10121
10388
|
function unpinContextSource(session, requested) {
|
|
10122
|
-
const
|
|
10123
|
-
if (!
|
|
10124
|
-
const match = matchSource(
|
|
10389
|
+
const list = session.contextSources;
|
|
10390
|
+
if (!list?.length) return void 0;
|
|
10391
|
+
const match = matchSource(list, requested);
|
|
10125
10392
|
if (!match) return void 0;
|
|
10126
|
-
|
|
10393
|
+
list.splice(list.indexOf(match), 1);
|
|
10127
10394
|
return match.path;
|
|
10128
10395
|
}
|
|
10129
10396
|
function toggleMuteContextSource(session, requested) {
|
|
10130
|
-
const
|
|
10131
|
-
if (!
|
|
10132
|
-
const match = matchSource(
|
|
10397
|
+
const list = session.contextSources;
|
|
10398
|
+
if (!list?.length) return void 0;
|
|
10399
|
+
const match = matchSource(list, requested);
|
|
10133
10400
|
if (!match) return void 0;
|
|
10134
10401
|
match.state = match.state === "muted" ? "pinned" : "muted";
|
|
10135
10402
|
return match;
|
|
10136
10403
|
}
|
|
10137
10404
|
async function resolvePinnedContent(session, workspace) {
|
|
10138
|
-
const
|
|
10139
|
-
if (!
|
|
10405
|
+
const list = session.contextSources;
|
|
10406
|
+
if (!list?.length) return [];
|
|
10140
10407
|
const resolved = [];
|
|
10141
10408
|
let remaining = MAX_PINNED_CHARS;
|
|
10142
|
-
for (const source of
|
|
10409
|
+
for (const source of list) {
|
|
10143
10410
|
if (source.state !== "pinned") continue;
|
|
10144
10411
|
if (remaining <= 0) break;
|
|
10145
10412
|
try {
|
|
@@ -10171,10 +10438,10 @@ These files were explicitly pinned by the user and are re-read from disk each tu
|
|
|
10171
10438
|
${blocks.join("\n\n")}
|
|
10172
10439
|
</pinned-context>`;
|
|
10173
10440
|
}
|
|
10174
|
-
function matchSource(
|
|
10441
|
+
function matchSource(list, requested) {
|
|
10175
10442
|
const trimmed = requested.trim();
|
|
10176
10443
|
if (!trimmed) return void 0;
|
|
10177
|
-
return
|
|
10444
|
+
return list.find((source) => source.path === trimmed) ?? list.find((source) => source.path.endsWith(`/${trimmed}`)) ?? list.find((source) => source.path.includes(trimmed));
|
|
10178
10445
|
}
|
|
10179
10446
|
function escapeAttribute5(value) {
|
|
10180
10447
|
return value.replace(/[&"<>]/g, (character) => ({
|
|
@@ -10780,11 +11047,21 @@ var AgentRunner = class {
|
|
|
10780
11047
|
await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
|
|
10781
11048
|
}
|
|
10782
11049
|
activeRunContract = contractEnabled ? this.session.taskContract : void 0;
|
|
11050
|
+
let verificationAttempted = false;
|
|
11051
|
+
const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
|
|
11052
|
+
const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
|
|
11053
|
+
if (this.contextManager.shouldCompact(this.session, contextBudget)) {
|
|
11054
|
+
const compacted = await this.compactContext(void 0, options.signal, "automatic");
|
|
11055
|
+
if (compacted.status === "compacted") {
|
|
11056
|
+
await emit({ type: "context_compacted", ...compacted });
|
|
11057
|
+
}
|
|
11058
|
+
}
|
|
10783
11059
|
const promptSections = [
|
|
10784
11060
|
`intent:${turnDirective.intent}`,
|
|
10785
11061
|
...workspaceRules ? ["rules"] : [],
|
|
10786
11062
|
...this.session.workingMemory ? ["working-memory"] : [],
|
|
10787
|
-
...contractEnabled ? ["task-contract"] : [],
|
|
11063
|
+
...contractEnabled && !this.session.compactedThroughMessageId ? ["task-contract"] : [],
|
|
11064
|
+
...this.session.compactedThroughMessageId ? ["compaction-facts"] : [],
|
|
10788
11065
|
...this.session.contextSummary ? ["session-summary"] : [],
|
|
10789
11066
|
...!trivialTurn ? [`context:${packed.engine}`] : [],
|
|
10790
11067
|
...packed.text ? [`code:${packed.engine}`] : [],
|
|
@@ -10792,13 +11069,6 @@ var AgentRunner = class {
|
|
|
10792
11069
|
...augmentation.skills?.length ? [`skills:${augmentation.skills.length}`] : [],
|
|
10793
11070
|
...augmentation.memoryCount ? [`memory:${augmentation.memoryCount}`] : []
|
|
10794
11071
|
];
|
|
10795
|
-
let verificationAttempted = false;
|
|
10796
|
-
const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
|
|
10797
|
-
const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
|
|
10798
|
-
if (this.contextManager.shouldCompact(this.session, contextBudget)) {
|
|
10799
|
-
const compacted = await this.compactContext(void 0, options.signal);
|
|
10800
|
-
await emit({ type: "context_compacted", ...compacted });
|
|
10801
|
-
}
|
|
10802
11072
|
for (let turn = 1; turn <= maxTurns; turn += 1) {
|
|
10803
11073
|
throwIfAborted(options.signal);
|
|
10804
11074
|
if (this.session.usage.inputTokens + this.session.usage.outputTokens >= this.config.agent.maxSessionTokens) {
|
|
@@ -10859,7 +11129,7 @@ var AgentRunner = class {
|
|
|
10859
11129
|
breakdown
|
|
10860
11130
|
});
|
|
10861
11131
|
}
|
|
10862
|
-
const assistantId =
|
|
11132
|
+
const assistantId = randomUUID13();
|
|
10863
11133
|
const response = await this.completeModel(
|
|
10864
11134
|
messages,
|
|
10865
11135
|
visibleTools,
|
|
@@ -11352,7 +11622,7 @@ ${completeContent}`;
|
|
|
11352
11622
|
}
|
|
11353
11623
|
appendAudit(event) {
|
|
11354
11624
|
const audit = this.session.audit ?? (this.session.audit = []);
|
|
11355
|
-
audit.push({ id:
|
|
11625
|
+
audit.push({ id: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
11356
11626
|
if (audit.length > 5e3) audit.splice(0, audit.length - 5e3);
|
|
11357
11627
|
}
|
|
11358
11628
|
async acceptChangedFiles(paths) {
|
|
@@ -11373,7 +11643,7 @@ ${completeContent}`;
|
|
|
11373
11643
|
const results = [];
|
|
11374
11644
|
for (const command2 of this.config.agent.verifyCommands) {
|
|
11375
11645
|
const call = {
|
|
11376
|
-
id: `verify-${
|
|
11646
|
+
id: `verify-${randomUUID13()}`,
|
|
11377
11647
|
name: "shell",
|
|
11378
11648
|
arguments: { command: command2, cwd: this.workspace.primaryRoot }
|
|
11379
11649
|
};
|
|
@@ -11399,13 +11669,25 @@ ${completeContent}`;
|
|
|
11399
11669
|
return [];
|
|
11400
11670
|
}
|
|
11401
11671
|
}
|
|
11402
|
-
async compactContext(instructions, signal) {
|
|
11672
|
+
async compactContext(instructions, signal, mode = "manual") {
|
|
11403
11673
|
const result = await this.contextManager.compact(
|
|
11404
11674
|
this.session,
|
|
11405
11675
|
this.provider,
|
|
11406
11676
|
signal,
|
|
11407
|
-
instructions
|
|
11677
|
+
instructions,
|
|
11678
|
+
mode
|
|
11408
11679
|
);
|
|
11680
|
+
if (result.status === "compacted") {
|
|
11681
|
+
recordTokenUsage(
|
|
11682
|
+
this.session,
|
|
11683
|
+
result.receipt.actual,
|
|
11684
|
+
result.receipt.estimated.inputTokens,
|
|
11685
|
+
result.receipt.estimated.outputTokens
|
|
11686
|
+
);
|
|
11687
|
+
}
|
|
11688
|
+
const receipts = this.session.contextCompactionReceipts ?? (this.session.contextCompactionReceipts = []);
|
|
11689
|
+
receipts.push(result.receipt);
|
|
11690
|
+
if (receipts.length > 64) receipts.splice(0, receipts.length - 64);
|
|
11409
11691
|
await this.persist();
|
|
11410
11692
|
return result;
|
|
11411
11693
|
}
|
|
@@ -11497,7 +11779,7 @@ ${completeContent}`;
|
|
|
11497
11779
|
};
|
|
11498
11780
|
function message(role, content, extra = {}) {
|
|
11499
11781
|
return {
|
|
11500
|
-
id:
|
|
11782
|
+
id: randomUUID13(),
|
|
11501
11783
|
role,
|
|
11502
11784
|
content,
|
|
11503
11785
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11894,7 +12176,7 @@ function integer(value, fallback, min, max) {
|
|
|
11894
12176
|
}
|
|
11895
12177
|
|
|
11896
12178
|
// src/agent/delegation.ts
|
|
11897
|
-
import { randomUUID as
|
|
12179
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
11898
12180
|
import { join as join18 } from "node:path";
|
|
11899
12181
|
import { z as z19 } from "zod";
|
|
11900
12182
|
|
|
@@ -12023,7 +12305,7 @@ function numeric(...values) {
|
|
|
12023
12305
|
}
|
|
12024
12306
|
|
|
12025
12307
|
// src/agent/team-store.ts
|
|
12026
|
-
import { createHash as createHash16, randomUUID as
|
|
12308
|
+
import { createHash as createHash16, randomUUID as randomUUID14 } from "node:crypto";
|
|
12027
12309
|
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
12028
12310
|
import { join as join16, resolve as resolve18 } from "node:path";
|
|
12029
12311
|
import { z as z18 } from "zod";
|
|
@@ -12117,7 +12399,7 @@ var TeamRunStore = class {
|
|
|
12117
12399
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12118
12400
|
const manifest = manifestSchema2.parse({
|
|
12119
12401
|
version: 2,
|
|
12120
|
-
id:
|
|
12402
|
+
id: randomUUID14(),
|
|
12121
12403
|
workspace: this.workspace,
|
|
12122
12404
|
objective: input2.objective,
|
|
12123
12405
|
reviewer: input2.reviewer,
|
|
@@ -12923,7 +13205,7 @@ var DelegationManager = class {
|
|
|
12923
13205
|
maxReviewRounds: 0
|
|
12924
13206
|
});
|
|
12925
13207
|
await emit?.({ type: "team_start", id: board.id, objective: task });
|
|
12926
|
-
const writerId =
|
|
13208
|
+
const writerId = randomUUID15();
|
|
12927
13209
|
await emit?.({ type: "agent_queued", id: writerId, profile: profile.name, task, phase: "write" });
|
|
12928
13210
|
const draft = await this.writerLane.createDraft(
|
|
12929
13211
|
Math.min(this.team.maxWriterPatchBytes ?? 6e4, 12e4),
|
|
@@ -13293,7 +13575,7 @@ You are the only writer inside a disposable worktree. Make only the bounded requ
|
|
|
13293
13575
|
const reviewer = reviewerOverride ?? this.team.reviewerProfile ?? "reviewer";
|
|
13294
13576
|
const rounds = Math.max(0, this.team.maxReviewRounds ?? 1);
|
|
13295
13577
|
const board = await this.teamStore?.create({ objective, reviewer, maxReviewRounds: rounds });
|
|
13296
|
-
const runId = board?.id ??
|
|
13578
|
+
const runId = board?.id ?? randomUUID15();
|
|
13297
13579
|
await emit?.({ type: "team_start", id: runId, objective });
|
|
13298
13580
|
try {
|
|
13299
13581
|
let results = await this.runBatch(board?.id, tasks, "work", emit, signal);
|
|
@@ -13351,7 +13633,7 @@ ${review.summary}`,
|
|
|
13351
13633
|
}
|
|
13352
13634
|
}
|
|
13353
13635
|
async runBatch(runId, tasks, phase, emit, signal) {
|
|
13354
|
-
const scheduled = tasks.map((task) => ({ id:
|
|
13636
|
+
const scheduled = tasks.map((task) => ({ id: randomUUID15(), task }));
|
|
13355
13637
|
for (const item of scheduled) {
|
|
13356
13638
|
await emit?.({ type: "agent_queued", id: item.id, profile: item.task.profile, task: item.task.task, phase });
|
|
13357
13639
|
}
|
|
@@ -13460,12 +13742,12 @@ Start the response with exactly VERDICT: ACCEPT when the evidence is sufficient,
|
|
|
13460
13742
|
});
|
|
13461
13743
|
}
|
|
13462
13744
|
async peerMessage(runId, from, to, content, emit) {
|
|
13463
|
-
const id =
|
|
13745
|
+
const id = randomUUID15();
|
|
13464
13746
|
if (runId) await this.teamStore?.recordMessage(runId, { id, from, to, content });
|
|
13465
13747
|
await emit?.({ type: "agent_message", id, from, to, content });
|
|
13466
13748
|
}
|
|
13467
13749
|
async runOne(task, phase, emit, signal, retryOf, scheduledId) {
|
|
13468
|
-
const id = scheduledId ??
|
|
13750
|
+
const id = scheduledId ?? randomUUID15();
|
|
13469
13751
|
const profile = this.options.profiles.get(task.profile);
|
|
13470
13752
|
const configuredRoute = this.team.routes?.[task.profile];
|
|
13471
13753
|
const budgetMode = configuredRoute?.budgetMode ?? this.team.budgetMode ?? "observe";
|
|
@@ -14532,6 +14814,7 @@ function sessionSummary(session) {
|
|
|
14532
14814
|
changedFiles: session.changedFiles,
|
|
14533
14815
|
...session.lastRun ? { lastRun: session.lastRun } : {},
|
|
14534
14816
|
...session.tokenLedger?.length ? { tokenLedger: session.tokenLedger } : {},
|
|
14817
|
+
...session.contextCompactionReceipts?.length ? { contextCompactionReceipts: session.contextCompactionReceipts } : {},
|
|
14535
14818
|
usage: session.usage
|
|
14536
14819
|
};
|
|
14537
14820
|
}
|
|
@@ -15952,12 +16235,13 @@ function MeterBar({ segments, total, width, glyphs }) {
|
|
|
15952
16235
|
function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact: compact2 = false, minimal = false, glyphMode = "auto" }) {
|
|
15953
16236
|
const theme = useTheme();
|
|
15954
16237
|
const glyphs = resolveGlyphs(glyphMode);
|
|
16238
|
+
const hasCompactedContext = status.compactedMessages > 0 || Boolean(summary);
|
|
15955
16239
|
if (minimal) {
|
|
15956
16240
|
const rowWidth2 = safeWidth(width);
|
|
15957
16241
|
const padding = rowWidth2 >= 4 ? 2 : 0;
|
|
15958
16242
|
const innerWidth2 = Math.max(1, rowWidth2 - padding);
|
|
15959
16243
|
const active = `${status.messageCount} msg ${glyphs.separator} ~${formatTokens(status.activeTokens)} tok`;
|
|
15960
|
-
const focus = sanitizeTerminalText(working?.focus || working?.goal || (
|
|
16244
|
+
const focus = sanitizeTerminalText(working?.focus || working?.goal || (hasCompactedContext ? "handoff ready" : "not established")).replace(/\s+/g, " ").trim() || "not established";
|
|
15961
16245
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: padding, children: [
|
|
15962
16246
|
/* @__PURE__ */ jsx(Text, { bold: true, color: theme.textStrong, children: truncateDisplay(`Context ${formatPercent(status.pressure)} ${glyphs.separator} ${active}`, innerWidth2) }),
|
|
15963
16247
|
/* @__PURE__ */ jsx(Text, { color: working ? theme.text : theme.muted, children: truncateDisplay(`working ${focus}`, innerWidth2) })
|
|
@@ -15966,7 +16250,7 @@ function ContextInspector({ status, working, summary, width, memory, connections
|
|
|
15966
16250
|
const entries = [
|
|
15967
16251
|
{ label: "active", detail: `${status.messageCount} messages ${glyphs.separator} ~${formatTokens(status.activeTokens)} tokens ${glyphs.separator} tools ~${formatTokens(status.toolTokens)}` },
|
|
15968
16252
|
{ label: "short-term", detail: working ? `${working.focus || working.goal || "ready"} ${glyphs.separator} ${relativeTime(working.lastUpdatedAt)}` : "not established" },
|
|
15969
|
-
{ label: "summary", detail:
|
|
16253
|
+
{ label: "summary", detail: hasCompactedContext ? `~${formatTokens(status.summaryTokens)} tokens ${glyphs.separator} ${status.compactedMessages} compacted${summary ? "" : ` ${glyphs.separator} facts`}` : "not created" },
|
|
15970
16254
|
{ label: "long-term", detail: memory ?? `retrieved by relevance ${glyphs.separator} untrusted context` }
|
|
15971
16255
|
];
|
|
15972
16256
|
if (!compact2 && working?.constraints.length) entries.push({ label: `constraints ${working.constraints.length}`, detail: working.constraints.slice(0, 2).join(` ${glyphs.separator} `) });
|
|
@@ -15988,7 +16272,7 @@ function ContextInspector({ status, working, summary, width, memory, connections
|
|
|
15988
16272
|
const rowWidth = safeWidth(width);
|
|
15989
16273
|
const innerWidth = Math.max(1, rowWidth - 2);
|
|
15990
16274
|
const pressureColor = status.pressure >= 0.9 ? theme.error : status.pressure >= 0.75 ? theme.warning : theme.accent;
|
|
15991
|
-
const summaryTokens =
|
|
16275
|
+
const summaryTokens = hasCompactedContext ? status.summaryTokens : 0;
|
|
15992
16276
|
const segments = [
|
|
15993
16277
|
{ label: "active", value: status.activeTokens, color: theme.accent },
|
|
15994
16278
|
{ label: "tools", value: status.toolTokens, color: theme.assistant },
|
|
@@ -17801,8 +18085,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17801
18085
|
return true;
|
|
17802
18086
|
}
|
|
17803
18087
|
if (subcommand === "list") {
|
|
17804
|
-
const
|
|
17805
|
-
appendList("Pinned context",
|
|
18088
|
+
const list = runner.listContextSources();
|
|
18089
|
+
appendList("Pinned context", list.length ? list.map((source) => ({
|
|
17806
18090
|
label: `${source.state === "muted" ? "muted " : "pinned"} ${source.path}`,
|
|
17807
18091
|
detail: `~${source.tokens} tokens${separator}added ${source.addedAt.slice(0, 10)}`,
|
|
17808
18092
|
tone: source.state === "muted" ? "warning" : "success"
|