@cognipeer/agent-sdk 0.10.1 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +251 -78
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.mjs +251 -78
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4601,18 +4601,60 @@ function createToolsNode(initialTools, opts) {
|
|
|
4601
4601
|
toolStateRef.__currentToolCallId = toolCallId;
|
|
4602
4602
|
delete toolStateRef.__awaitingApproval;
|
|
4603
4603
|
}
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4604
|
+
const stableJson = (value) => {
|
|
4605
|
+
try {
|
|
4606
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
4607
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
4608
|
+
const keys = Object.keys(value).sort();
|
|
4609
|
+
return `{${keys.map((k) => JSON.stringify(k) + ":" + stableJson(value[k])).join(",")}}`;
|
|
4610
|
+
} catch {
|
|
4611
|
+
return String(value);
|
|
4612
|
+
}
|
|
4613
|
+
};
|
|
4614
|
+
const rejectByPolicy = (gateReason, gatedArgs) => {
|
|
4615
|
+
const reason = gateReason || `Tool call blocked by policy: ${toolName}`;
|
|
4616
|
+
push({ role: "tool", content: reason, tool_call_id: toolCallId, name: tc.name });
|
|
4617
|
+
toolHistory.push({
|
|
4618
|
+
executionId: nanoid.nanoid(),
|
|
4619
|
+
toolName,
|
|
4620
|
+
args: gatedArgs,
|
|
4621
|
+
output: reason,
|
|
4622
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4623
|
+
tool_call_id: toolCallId,
|
|
4624
|
+
status: "rejected"
|
|
4625
|
+
});
|
|
4626
|
+
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args: gatedArgs, error: { message: reason } });
|
|
4627
|
+
recordTraceEvent(traceSession, {
|
|
4628
|
+
type: "tool_call",
|
|
4629
|
+
label: `Tool Denied - ${toolName}`,
|
|
4630
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
4631
|
+
status: "skipped",
|
|
4632
|
+
toolDetails,
|
|
4633
|
+
toolExecutionId: toolCallId,
|
|
4634
|
+
sections: buildToolTraceSections({
|
|
4635
|
+
args: gatedArgs,
|
|
4636
|
+
executionId: toolCallId,
|
|
4637
|
+
output: reason,
|
|
4638
|
+
status: "skipped",
|
|
4639
|
+
summary: reason,
|
|
4640
|
+
toolDetails,
|
|
4641
|
+
toolName
|
|
4642
|
+
})
|
|
4643
|
+
});
|
|
4644
|
+
toolCount += 1;
|
|
4645
|
+
return { status: "error" };
|
|
4646
|
+
};
|
|
4647
|
+
const applyPreToolUseGate = async (candidate) => {
|
|
4648
|
+
if (!host?.has("preToolUse")) return { ok: true, args: candidate, ask: false };
|
|
4608
4649
|
const gate = await host.runGate("preToolUse", {
|
|
4609
4650
|
toolName,
|
|
4610
4651
|
toolCallId,
|
|
4611
|
-
args,
|
|
4652
|
+
args: candidate,
|
|
4612
4653
|
tool,
|
|
4613
4654
|
executionCount: countSuccessfulToolExecutions(toolHistory, toolName)
|
|
4614
4655
|
});
|
|
4615
|
-
|
|
4656
|
+
let next = candidate;
|
|
4657
|
+
if (gate.input.args !== candidate) {
|
|
4616
4658
|
const revalidated = validateToolArgs(tool, gate.input.args);
|
|
4617
4659
|
if (!revalidated.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
4618
4660
|
const message = `Tool argument validation failed after a plugin rewrite for ${toolName}: ${revalidated.message}`;
|
|
@@ -4620,48 +4662,31 @@ function createToolsNode(initialTools, opts) {
|
|
|
4620
4662
|
markToolFailure(message, toolName, toolCallId);
|
|
4621
4663
|
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args: gate.input.args, error: { message } });
|
|
4622
4664
|
toolCount += 1;
|
|
4623
|
-
return { status: "error" };
|
|
4665
|
+
return { ok: false, outcome: { status: "error" } };
|
|
4624
4666
|
}
|
|
4625
|
-
|
|
4667
|
+
next = revalidated.ok ? revalidated.value : gate.input.args;
|
|
4626
4668
|
}
|
|
4627
4669
|
if (gate.decision === "deny") {
|
|
4628
|
-
|
|
4629
|
-
push({ role: "tool", content: reason, tool_call_id: toolCallId, name: tc.name });
|
|
4630
|
-
toolHistory.push({
|
|
4631
|
-
executionId: nanoid.nanoid(),
|
|
4632
|
-
toolName,
|
|
4633
|
-
args,
|
|
4634
|
-
output: reason,
|
|
4635
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4636
|
-
tool_call_id: toolCallId,
|
|
4637
|
-
status: "rejected"
|
|
4638
|
-
});
|
|
4639
|
-
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args, error: { message: reason } });
|
|
4640
|
-
recordTraceEvent(traceSession, {
|
|
4641
|
-
type: "tool_call",
|
|
4642
|
-
label: `Tool Denied - ${toolName}`,
|
|
4643
|
-
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
4644
|
-
status: "skipped",
|
|
4645
|
-
toolDetails,
|
|
4646
|
-
toolExecutionId: toolCallId,
|
|
4647
|
-
sections: buildToolTraceSections({
|
|
4648
|
-
args,
|
|
4649
|
-
executionId: toolCallId,
|
|
4650
|
-
output: reason,
|
|
4651
|
-
status: "skipped",
|
|
4652
|
-
summary: reason,
|
|
4653
|
-
toolDetails,
|
|
4654
|
-
toolName
|
|
4655
|
-
})
|
|
4656
|
-
});
|
|
4657
|
-
toolCount += 1;
|
|
4658
|
-
return { status: "error" };
|
|
4659
|
-
}
|
|
4660
|
-
if (gate.decision === "ask") {
|
|
4661
|
-
hookAskedForApproval = true;
|
|
4662
|
-
hookApprovalPrompt = gate.approvalPrompt;
|
|
4670
|
+
return { ok: false, outcome: rejectByPolicy(gate.reason, next) };
|
|
4663
4671
|
}
|
|
4664
|
-
|
|
4672
|
+
return {
|
|
4673
|
+
ok: true,
|
|
4674
|
+
args: next,
|
|
4675
|
+
ask: gate.decision === "ask",
|
|
4676
|
+
approvalPrompt: gate.approvalPrompt,
|
|
4677
|
+
result: gate.shortCircuit
|
|
4678
|
+
};
|
|
4679
|
+
};
|
|
4680
|
+
let hookAskedForApproval = false;
|
|
4681
|
+
let hookApprovalPrompt;
|
|
4682
|
+
let hookResult;
|
|
4683
|
+
{
|
|
4684
|
+
const gated = await applyPreToolUseGate(args);
|
|
4685
|
+
if (!gated.ok) return gated.outcome;
|
|
4686
|
+
args = gated.args;
|
|
4687
|
+
hookAskedForApproval = gated.ask;
|
|
4688
|
+
hookApprovalPrompt = gated.approvalPrompt;
|
|
4689
|
+
if (gated.result !== void 0) hookResult = gated.result;
|
|
4665
4690
|
}
|
|
4666
4691
|
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
4667
4692
|
if (maxExecutionsPerRun !== null) {
|
|
@@ -4746,7 +4771,34 @@ function createToolsNode(initialTools, opts) {
|
|
|
4746
4771
|
return { status: "rejected", approval: approvalEntry };
|
|
4747
4772
|
}
|
|
4748
4773
|
if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
|
|
4749
|
-
|
|
4774
|
+
const approvedArgs = approvalEntry.approvedArgs;
|
|
4775
|
+
if (stableJson(approvedArgs) !== stableJson(args)) {
|
|
4776
|
+
const settleRefused = (resolution) => {
|
|
4777
|
+
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution };
|
|
4778
|
+
approvalEntry.status = "executed";
|
|
4779
|
+
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4780
|
+
pendingByCallId.set(toolCallId, approvalEntry);
|
|
4781
|
+
};
|
|
4782
|
+
const revalidated = validateToolArgs(tool, approvedArgs);
|
|
4783
|
+
if (!revalidated.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
4784
|
+
const message = `Tool argument validation failed for reviewer-edited arguments of ${toolName}: ${revalidated.message}`;
|
|
4785
|
+
push({ role: "tool", content: message, tool_call_id: toolCallId, name: tc.name });
|
|
4786
|
+
markToolFailure(message, toolName, toolCallId);
|
|
4787
|
+
settleRefused("invalid_args");
|
|
4788
|
+
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args: approvedArgs, error: { message } });
|
|
4789
|
+
toolCount += 1;
|
|
4790
|
+
return { status: "error", approval: approvalEntry };
|
|
4791
|
+
}
|
|
4792
|
+
const gated = await applyPreToolUseGate(revalidated.ok ? revalidated.value : approvedArgs);
|
|
4793
|
+
if (!gated.ok) {
|
|
4794
|
+
settleRefused("policy_denied");
|
|
4795
|
+
return { ...gated.outcome, approval: approvalEntry };
|
|
4796
|
+
}
|
|
4797
|
+
args = gated.args;
|
|
4798
|
+
if (gated.result !== void 0 && hookResult === void 0) hookResult = gated.result;
|
|
4799
|
+
} else {
|
|
4800
|
+
args = approvedArgs;
|
|
4801
|
+
}
|
|
4750
4802
|
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
4751
4803
|
}
|
|
4752
4804
|
}
|
|
@@ -4756,16 +4808,6 @@ function createToolsNode(initialTools, opts) {
|
|
|
4756
4808
|
const cacheCfg = tool.cache;
|
|
4757
4809
|
const cacheEnabled = cacheCfg === true || cacheCfg && typeof cacheCfg === "object";
|
|
4758
4810
|
const cacheKeyFn = cacheEnabled && typeof cacheCfg === "object" ? cacheCfg.keyFn : void 0;
|
|
4759
|
-
const stableJson = (value) => {
|
|
4760
|
-
try {
|
|
4761
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
4762
|
-
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
4763
|
-
const keys = Object.keys(value).sort();
|
|
4764
|
-
return `{${keys.map((k) => JSON.stringify(k) + ":" + stableJson(value[k])).join(",")}}`;
|
|
4765
|
-
} catch {
|
|
4766
|
-
return String(value);
|
|
4767
|
-
}
|
|
4768
|
-
};
|
|
4769
4811
|
const cacheKey = cacheEnabled ? `${toolName}|${cacheKeyFn ? cacheKeyFn(args) : stableJson(args)}` : void 0;
|
|
4770
4812
|
const toolCache = state.toolCache = state.toolCache || {};
|
|
4771
4813
|
let cachedHit = void 0;
|
|
@@ -4813,6 +4855,22 @@ function createToolsNode(initialTools, opts) {
|
|
|
4813
4855
|
try {
|
|
4814
4856
|
if (cachedHit !== void 0) {
|
|
4815
4857
|
const executionId2 = nanoid.nanoid();
|
|
4858
|
+
if (host?.has("postToolUse")) {
|
|
4859
|
+
const shortGate = await host.runGate("postToolUse", {
|
|
4860
|
+
toolName,
|
|
4861
|
+
toolCallId,
|
|
4862
|
+
args,
|
|
4863
|
+
output: cachedHit,
|
|
4864
|
+
durationMs: 0,
|
|
4865
|
+
executionId: executionId2,
|
|
4866
|
+
source: shortCircuitSource === "plugin" ? "hook" : "cache"
|
|
4867
|
+
});
|
|
4868
|
+
if (shortGate.decision === "deny") {
|
|
4869
|
+
cachedHit = shortGate.reason || `Tool output withheld by policy: ${toolName}`;
|
|
4870
|
+
} else if (shortGate.input.output !== cachedHit) {
|
|
4871
|
+
cachedHit = shortGate.input.output;
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4816
4874
|
const { output: compressedCachedHit, stats: cachedContextPilotStats } = maybeCompressToolOutput(toolName, cachedHit, executionId2);
|
|
4817
4875
|
const responsePolicy2 = applyToolResponseHardCap(toolName, compressedCachedHit, executionId2, resolved);
|
|
4818
4876
|
toolHistory.push({
|
|
@@ -4979,7 +5037,8 @@ function createToolsNode(initialTools, opts) {
|
|
|
4979
5037
|
args,
|
|
4980
5038
|
output,
|
|
4981
5039
|
durationMs,
|
|
4982
|
-
executionId
|
|
5040
|
+
executionId,
|
|
5041
|
+
source: "tool"
|
|
4983
5042
|
});
|
|
4984
5043
|
if (postGate.decision === "deny") {
|
|
4985
5044
|
output = postGate.reason || `Tool output withheld by policy: ${toolName}`;
|
|
@@ -5074,7 +5133,8 @@ function createToolsNode(initialTools, opts) {
|
|
|
5074
5133
|
output: message,
|
|
5075
5134
|
error: error instanceof Error ? error : new Error(message),
|
|
5076
5135
|
durationMs,
|
|
5077
|
-
executionId
|
|
5136
|
+
executionId,
|
|
5137
|
+
source: "tool"
|
|
5078
5138
|
});
|
|
5079
5139
|
if (errorGate.decision === "deny") {
|
|
5080
5140
|
message = errorGate.reason || `Tool error withheld by policy: ${toolName}`;
|
|
@@ -5750,7 +5810,14 @@ var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set([
|
|
|
5750
5810
|
// Session bookkeeping: a resumed run must open its OWN session, so these
|
|
5751
5811
|
// must not travel in a snapshot.
|
|
5752
5812
|
"__pluginRunStarted",
|
|
5753
|
-
"__pluginRunEnded"
|
|
5813
|
+
"__pluginRunEnded",
|
|
5814
|
+
// Set by restoreSnapshot on the way OUT, consumed by the next invoke. Never
|
|
5815
|
+
// persisted: a snapshot of a restored run would otherwise mark every later
|
|
5816
|
+
// restore as "resumed" forever.
|
|
5817
|
+
"__restoredFromSnapshot",
|
|
5818
|
+
// A per-turn verdict. It describes the run that produced the snapshot, not
|
|
5819
|
+
// the run that will resume from it.
|
|
5820
|
+
"__guardrailBlocked"
|
|
5754
5821
|
]);
|
|
5755
5822
|
var clone = (value) => {
|
|
5756
5823
|
if (typeof globalThis.structuredClone === "function") {
|
|
@@ -6089,6 +6156,9 @@ function isSyntheticSummaryMessage(message) {
|
|
|
6089
6156
|
}
|
|
6090
6157
|
return false;
|
|
6091
6158
|
}
|
|
6159
|
+
function isCompactedToolContent(content) {
|
|
6160
|
+
return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE") || content.startsWith("ARCHIVED_TOOL_RESPONSE") || content.startsWith("STRUCTURED_TOOL_RESPONSE") || content.startsWith("DROPPED_TOOL_RESPONSE"));
|
|
6161
|
+
}
|
|
6092
6162
|
|
|
6093
6163
|
// src/structuredOutput/manager.ts
|
|
6094
6164
|
var StructuredOutputManager = class {
|
|
@@ -6374,6 +6444,7 @@ var SLOT_NAMES = [
|
|
|
6374
6444
|
"contextBuilder"
|
|
6375
6445
|
];
|
|
6376
6446
|
var DECISION_RANK = { allow: 0, ask: 1, deny: 2 };
|
|
6447
|
+
var isHookDecision = (value) => typeof value === "string" && Object.prototype.hasOwnProperty.call(DECISION_RANK, value);
|
|
6377
6448
|
function escalate(current, next) {
|
|
6378
6449
|
return DECISION_RANK[next] > DECISION_RANK[current] ? next : current;
|
|
6379
6450
|
}
|
|
@@ -6383,6 +6454,15 @@ var HookTimeoutError = class extends Error {
|
|
|
6383
6454
|
this.name = "HookTimeoutError";
|
|
6384
6455
|
}
|
|
6385
6456
|
};
|
|
6457
|
+
var InvalidHookDecisionError = class extends Error {
|
|
6458
|
+
constructor(plugin, hook, value) {
|
|
6459
|
+
const shown = typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
6460
|
+
super(
|
|
6461
|
+
`plugin "${plugin}" hook "${hook}" returned an unknown decision ${shown}; expected "allow", "ask" or "deny"`
|
|
6462
|
+
);
|
|
6463
|
+
this.name = "InvalidHookDecisionError";
|
|
6464
|
+
}
|
|
6465
|
+
};
|
|
6386
6466
|
function withTimeout(value, ms, label) {
|
|
6387
6467
|
if (!ms || !Number.isFinite(ms) || ms <= 0) return Promise.resolve(value);
|
|
6388
6468
|
return new Promise((resolve, reject) => {
|
|
@@ -6672,10 +6752,18 @@ function createPluginHost(plugins = [], options = {}) {
|
|
|
6672
6752
|
`plugin "${entry.plugin}" hook "${hook}"`
|
|
6673
6753
|
);
|
|
6674
6754
|
output = raw ?? void 0;
|
|
6755
|
+
if (output && typeof output === "object" && output.decision !== void 0 && !isHookDecision(output.decision)) {
|
|
6756
|
+
throw new InvalidHookDecisionError(entry.plugin, hook, output.decision);
|
|
6757
|
+
}
|
|
6675
6758
|
} catch (err) {
|
|
6676
6759
|
const message = err instanceof Error ? err.message : String(err);
|
|
6677
6760
|
const timedOut = err instanceof HookTimeoutError;
|
|
6678
|
-
|
|
6761
|
+
const badDecision = err instanceof InvalidHookDecisionError;
|
|
6762
|
+
if (badDecision) {
|
|
6763
|
+
logger.error(`plugin "${entry.plugin}" hook "${hook}" returned an invalid decision:`, message);
|
|
6764
|
+
} else {
|
|
6765
|
+
logger.warn(`plugin "${entry.plugin}" hook "${hook}" failed:`, message);
|
|
6766
|
+
}
|
|
6679
6767
|
emitPluginEvent(entry, hook, {
|
|
6680
6768
|
phase: timedOut ? "timeout" : "error",
|
|
6681
6769
|
error: { message },
|
|
@@ -6687,8 +6775,9 @@ function createPluginHost(plugins = [], options = {}) {
|
|
|
6687
6775
|
durationMs: performanceNow() - startedAt
|
|
6688
6776
|
});
|
|
6689
6777
|
if (entry.failureMode === "closed") {
|
|
6778
|
+
const cause = timedOut ? "timed out" : badDecision ? "invalid decision" : "hook error";
|
|
6690
6779
|
result.decision = "deny";
|
|
6691
|
-
result.reason = `${entry.plugin}: ${
|
|
6780
|
+
result.reason = `${entry.plugin}: ${cause} (fail-closed) \u2014 ${message}`;
|
|
6692
6781
|
result.deniedBy = entry.plugin;
|
|
6693
6782
|
result.input = current;
|
|
6694
6783
|
return result;
|
|
@@ -6746,11 +6835,19 @@ function createPluginHost(plugins = [], options = {}) {
|
|
|
6746
6835
|
if (output.metadata && typeof output.metadata === "object") {
|
|
6747
6836
|
result.metadata = { ...result.metadata ?? {}, ...output.metadata };
|
|
6748
6837
|
}
|
|
6749
|
-
|
|
6838
|
+
let decision = output.decision;
|
|
6839
|
+
if (decision === "ask" && hook !== "preToolUse") {
|
|
6840
|
+
logger.warn(
|
|
6841
|
+
`plugin "${entry.plugin}" returned "ask" from "${hook}", which cannot pause for approval; treating it as "deny".`
|
|
6842
|
+
);
|
|
6843
|
+
decision = "deny";
|
|
6844
|
+
}
|
|
6750
6845
|
const escalated = decision ? escalate(result.decision, decision) : result.decision;
|
|
6751
6846
|
const escalatesHere = escalated !== result.decision;
|
|
6752
6847
|
result.decision = escalated;
|
|
6753
|
-
if (
|
|
6848
|
+
if (escalatesHere) {
|
|
6849
|
+
result.reason = typeof output.reason === "string" ? `${entry.plugin}: ${output.reason}` : void 0;
|
|
6850
|
+
} else if (typeof output.reason === "string" && !result.reason) {
|
|
6754
6851
|
result.reason = `${entry.plugin}: ${output.reason}`;
|
|
6755
6852
|
}
|
|
6756
6853
|
if (mutatedHere) {
|
|
@@ -6844,7 +6941,7 @@ async function openPluginSession(runHost, params) {
|
|
|
6844
6941
|
const appended = startGate.collected.systemPromptAppend ?? [];
|
|
6845
6942
|
const systemPromptAppend = appended.length > 0 ? appended.join("\n\n") : void 0;
|
|
6846
6943
|
const last = messages[messages.length - 1];
|
|
6847
|
-
if (params.
|
|
6944
|
+
if (params.pausedMidRun || last?.role !== "user" || !runHost.has("userPromptSubmit")) {
|
|
6848
6945
|
return { messages, systemPromptAppend };
|
|
6849
6946
|
}
|
|
6850
6947
|
const content = last.content;
|
|
@@ -7089,6 +7186,11 @@ function createAgent(opts) {
|
|
|
7089
7186
|
delete nextCtx.__paused;
|
|
7090
7187
|
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
7091
7188
|
}
|
|
7189
|
+
if (state.ctx?.__restoredFromSnapshot) {
|
|
7190
|
+
const nextCtx = { ...state.ctx };
|
|
7191
|
+
delete nextCtx.__restoredFromSnapshot;
|
|
7192
|
+
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
7193
|
+
}
|
|
7092
7194
|
let resumeStage = null;
|
|
7093
7195
|
if (state.ctx?.__resumeStage) {
|
|
7094
7196
|
const nextCtx = { ...state.ctx };
|
|
@@ -7363,7 +7465,7 @@ function createAgent(opts) {
|
|
|
7363
7465
|
continue;
|
|
7364
7466
|
}
|
|
7365
7467
|
if (toolCalls.length === 0) {
|
|
7366
|
-
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
|
|
7468
|
+
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize && !state.ctx?.__guardrailBlocked) {
|
|
7367
7469
|
if (soManager.strategy.kind === "native") {
|
|
7368
7470
|
const assistantText = extractMessageText2(lastMsg);
|
|
7369
7471
|
if (assistantText) {
|
|
@@ -7532,6 +7634,7 @@ function createAgent(opts) {
|
|
|
7532
7634
|
delete ctx.__plugins;
|
|
7533
7635
|
delete ctx.__applySystemPromptContribution;
|
|
7534
7636
|
delete ctx.__pluginState;
|
|
7637
|
+
delete ctx.__guardrailBlocked;
|
|
7535
7638
|
const inheritedSession = config?.__pluginSession;
|
|
7536
7639
|
ctx.__runId = inheritedSession?.runId ?? traceSession?.sessionId ?? `run_${nanoid.nanoid(10)}`;
|
|
7537
7640
|
const stateHolder = inheritedSession?.stateHolder ?? { value: void 0 };
|
|
@@ -7581,9 +7684,11 @@ function createAgent(opts) {
|
|
|
7581
7684
|
usage: input.usage || { perRequest: [], totals: {} }
|
|
7582
7685
|
};
|
|
7583
7686
|
stateHolder.value = initial;
|
|
7584
|
-
const
|
|
7585
|
-
input.ctx?.
|
|
7687
|
+
const pausedMidRun = Boolean(
|
|
7688
|
+
input.ctx?.__paused || input.ctx?.__resumeStage || input.ctx?.__awaitingApproval || input.ctx?.__awaitingUserQuestion
|
|
7586
7689
|
);
|
|
7690
|
+
const resumed = Boolean(input.ctx?.__restoredFromSnapshot || pausedMidRun);
|
|
7691
|
+
delete ctx.__restoredFromSnapshot;
|
|
7587
7692
|
const startedAt = Date.now();
|
|
7588
7693
|
let sessionEnded = false;
|
|
7589
7694
|
const endSession = async (status, payload) => {
|
|
@@ -7609,6 +7714,7 @@ function createAgent(opts) {
|
|
|
7609
7714
|
const opened = await openPluginSession(runHost, {
|
|
7610
7715
|
messages: initial.messages,
|
|
7611
7716
|
resumed,
|
|
7717
|
+
pausedMidRun,
|
|
7612
7718
|
config
|
|
7613
7719
|
});
|
|
7614
7720
|
if (opened.messages !== initial.messages) {
|
|
@@ -7969,6 +8075,10 @@ function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
|
7969
8075
|
function renderStructuredSummary(summary) {
|
|
7970
8076
|
if (!summary) return "";
|
|
7971
8077
|
const lines = ["Context summary:"];
|
|
8078
|
+
if (summary.user_directives && summary.user_directives.length > 0) {
|
|
8079
|
+
lines.push("User directives (still in force \u2014 follow them):");
|
|
8080
|
+
lines.push(...summary.user_directives.map((directive) => `- ${directive}`));
|
|
8081
|
+
}
|
|
7972
8082
|
if (summary.stable_facts.length > 0) {
|
|
7973
8083
|
lines.push("Stable facts:");
|
|
7974
8084
|
lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
|
|
@@ -8044,17 +8154,46 @@ function clampToBudget(messages, maxContextTokens) {
|
|
|
8044
8154
|
}
|
|
8045
8155
|
return working;
|
|
8046
8156
|
}
|
|
8157
|
+
function collectSummaryOnlyBody(body, hasSummary, lastTurnsToKeep) {
|
|
8158
|
+
if (!hasSummary) {
|
|
8159
|
+
return collectRecentTurns(body, Math.max(1, lastTurnsToKeep));
|
|
8160
|
+
}
|
|
8161
|
+
let boundary = -1;
|
|
8162
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
8163
|
+
if (isSyntheticSummaryMessage(body[index])) {
|
|
8164
|
+
boundary = index;
|
|
8165
|
+
break;
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
let lastUserIndex = -1;
|
|
8169
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
8170
|
+
if (body[index].role === "user") {
|
|
8171
|
+
lastUserIndex = index;
|
|
8172
|
+
break;
|
|
8173
|
+
}
|
|
8174
|
+
}
|
|
8175
|
+
const keep = /* @__PURE__ */ new Set();
|
|
8176
|
+
const firstUserIndex = body.findIndex((message) => message.role === "user");
|
|
8177
|
+
if (firstUserIndex >= 0) keep.add(firstUserIndex);
|
|
8178
|
+
const verbatimFrom = Math.min(lastUserIndex >= 0 ? lastUserIndex : body.length, boundary + 1);
|
|
8179
|
+
for (let index = Math.max(0, verbatimFrom); index < body.length; index += 1) {
|
|
8180
|
+
if (!isSyntheticSummaryMessage(body[index])) keep.add(index);
|
|
8181
|
+
}
|
|
8182
|
+
return body.filter((_, index) => keep.has(index));
|
|
8183
|
+
}
|
|
8047
8184
|
function buildModelMessages(state, config) {
|
|
8048
8185
|
const rawMessages = state.messages || [];
|
|
8049
8186
|
if (config.context.policy === "raw") {
|
|
8050
8187
|
return clampToBudget(rawMessages, config.limits.maxContextTokens);
|
|
8051
8188
|
}
|
|
8052
|
-
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
8053
8189
|
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
8054
8190
|
const summaryText = renderStructuredSummary(latestSummary);
|
|
8055
8191
|
const memoryText = renderMemoryBlock(state.memoryFacts);
|
|
8056
|
-
const systemMessage2 =
|
|
8057
|
-
const body = systemMessage2 ?
|
|
8192
|
+
const systemMessage2 = rawMessages[0]?.role === "system" ? rawMessages[0] : void 0;
|
|
8193
|
+
const body = config.context.policy === "summary_only" ? collectSummaryOnlyBody(systemMessage2 ? rawMessages.slice(1) : rawMessages, Boolean(summaryText), config.context.lastTurnsToKeep) : (() => {
|
|
8194
|
+
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
8195
|
+
return recentMessages[0]?.role === "system" ? recentMessages.slice(1) : recentMessages;
|
|
8196
|
+
})();
|
|
8058
8197
|
const syntheticContextMessages = [];
|
|
8059
8198
|
if (summaryText) {
|
|
8060
8199
|
syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
|
|
@@ -8065,7 +8204,7 @@ function buildModelMessages(state, config) {
|
|
|
8065
8204
|
const assembled = [
|
|
8066
8205
|
...systemMessage2 ? [systemMessage2] : [],
|
|
8067
8206
|
...syntheticContextMessages,
|
|
8068
|
-
...
|
|
8207
|
+
...body
|
|
8069
8208
|
];
|
|
8070
8209
|
return clampToBudget(assembled, config.limits.maxContextTokens);
|
|
8071
8210
|
}
|
|
@@ -8110,6 +8249,7 @@ function extractJsonObject(text) {
|
|
|
8110
8249
|
}
|
|
8111
8250
|
function normalizeStructuredSummary(input, fallbackText) {
|
|
8112
8251
|
const summary = {
|
|
8252
|
+
user_directives: Array.isArray(input?.user_directives) ? input.user_directives.map((directive) => String(directive ?? "").trim()).filter(Boolean) : [],
|
|
8113
8253
|
stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
|
|
8114
8254
|
key: String(fact.key || "unknown"),
|
|
8115
8255
|
value: String(fact.value || ""),
|
|
@@ -8170,7 +8310,7 @@ function extractCanonicalFacts(messages) {
|
|
|
8170
8310
|
return [...facts.values()];
|
|
8171
8311
|
}
|
|
8172
8312
|
function isSummarizedToolPlaceholder(content) {
|
|
8173
|
-
return
|
|
8313
|
+
return isCompactedToolContent(content);
|
|
8174
8314
|
}
|
|
8175
8315
|
function collectLatestTurnToolCallIds(messages) {
|
|
8176
8316
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -8229,11 +8369,16 @@ function runIntegrityCheck(previous, current) {
|
|
|
8229
8369
|
const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
|
|
8230
8370
|
const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
|
|
8231
8371
|
const obsoleteKeys = new Set(current.discarded_obsolete);
|
|
8232
|
-
const
|
|
8372
|
+
const currentDirectives = new Set(current.user_directives || []);
|
|
8373
|
+
const lostDirectives = (previous?.user_directives || []).filter((directive) => !currentDirectives.has(directive) && !obsoleteKeys.has(directive));
|
|
8374
|
+
const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key)) || lostDirectives.length > 0;
|
|
8233
8375
|
const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
|
|
8234
8376
|
if (criticalFactLoss) {
|
|
8235
8377
|
notes.push("Missing previously retained facts; merged forward during integrity repair.");
|
|
8236
8378
|
}
|
|
8379
|
+
if (lostDirectives.length > 0) {
|
|
8380
|
+
notes.push(`Dropped ${lostDirectives.length} user directive(s); merged forward during integrity repair.`);
|
|
8381
|
+
}
|
|
8237
8382
|
if (obsoleteFactRevived) {
|
|
8238
8383
|
notes.push("Obsolete facts reappeared; removed during integrity repair.");
|
|
8239
8384
|
}
|
|
@@ -8256,7 +8401,13 @@ function repairStructuredSummary(previous, current, integrity) {
|
|
|
8256
8401
|
}
|
|
8257
8402
|
}
|
|
8258
8403
|
const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
|
|
8259
|
-
|
|
8404
|
+
const directives = [...current.user_directives || []];
|
|
8405
|
+
for (const directive of previous.user_directives || []) {
|
|
8406
|
+
if (!directives.includes(directive) && !current.discarded_obsolete.includes(directive)) {
|
|
8407
|
+
directives.push(directive);
|
|
8408
|
+
}
|
|
8409
|
+
}
|
|
8410
|
+
return { ...current, user_directives: directives, stable_facts: filteredFacts };
|
|
8260
8411
|
}
|
|
8261
8412
|
function createContextSummarizeNode(opts) {
|
|
8262
8413
|
return async (state) => {
|
|
@@ -8330,6 +8481,7 @@ function createContextSummarizeNode(opts) {
|
|
|
8330
8481
|
|
|
8331
8482
|
Return exactly one JSON object with this schema:
|
|
8332
8483
|
{
|
|
8484
|
+
"user_directives": [string],
|
|
8333
8485
|
"stable_facts": [{ "key": string, "value": string, "confidence": number }],
|
|
8334
8486
|
"active_goals": [string],
|
|
8335
8487
|
"open_questions": [string],
|
|
@@ -8338,6 +8490,7 @@ Return exactly one JSON object with this schema:
|
|
|
8338
8490
|
}
|
|
8339
8491
|
|
|
8340
8492
|
Rules:
|
|
8493
|
+
- user_directives: standing instructions the user gave about HOW to work or answer for the rest of the conversation (language, format, tone, scope, things to always or never do) that still apply. NOT the tasks or questions themselves \u2014 "read the logs" is a task (an active goal), "always answer in Turkish" is a directive. Copy each directive close to verbatim; never merge, soften or drop one. Carry forward every directive from the previous structured summary unless the user explicitly revoked it \u2014 then put its exact text in discarded_obsolete.
|
|
8341
8494
|
- Keep stable_facts only for facts that future turns must remember.
|
|
8342
8495
|
- Put invalidated or superseded fact keys in discarded_obsolete.
|
|
8343
8496
|
- Preserve active_goals still relevant to the user.
|
|
@@ -8405,6 +8558,7 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
|
|
|
8405
8558
|
durationMs = Date.now() - startTime;
|
|
8406
8559
|
structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
|
|
8407
8560
|
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
8561
|
+
structuredSummary.user_directives = [...previousStructuredSummary?.user_directives || []];
|
|
8408
8562
|
integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
|
|
8409
8563
|
summaryText = renderStructuredSummary(structuredSummary);
|
|
8410
8564
|
const errorEvent = {
|
|
@@ -10265,6 +10419,11 @@ ${pluginAppend}`;
|
|
|
10265
10419
|
const instance = {
|
|
10266
10420
|
invoke: async (input, config) => {
|
|
10267
10421
|
await ensurePluginSetup();
|
|
10422
|
+
if (input?.ctx?.__guardrailBlocked !== void 0) {
|
|
10423
|
+
const clearedCtx = { ...input.ctx };
|
|
10424
|
+
delete clearedCtx.__guardrailBlocked;
|
|
10425
|
+
input = { ...input, ctx: clearedCtx };
|
|
10426
|
+
}
|
|
10268
10427
|
const sessionHolder = { value: input };
|
|
10269
10428
|
const emitSessionEvent = (event) => {
|
|
10270
10429
|
try {
|
|
@@ -10315,12 +10474,20 @@ ${pluginAppend}`;
|
|
|
10315
10474
|
stateHolder: sessionHolder
|
|
10316
10475
|
}
|
|
10317
10476
|
};
|
|
10318
|
-
const
|
|
10319
|
-
input.ctx?.
|
|
10477
|
+
const pausedMidRun = Boolean(
|
|
10478
|
+
input.ctx?.__paused || input.ctx?.__resumeStage || input.ctx?.__awaitingApproval || input.ctx?.__awaitingUserQuestion
|
|
10320
10479
|
);
|
|
10480
|
+
const resumed = Boolean(input.ctx?.__restoredFromSnapshot || pausedMidRun);
|
|
10481
|
+
if (input.ctx?.__restoredFromSnapshot) {
|
|
10482
|
+
const consumedCtx = { ...input.ctx };
|
|
10483
|
+
delete consumedCtx.__restoredFromSnapshot;
|
|
10484
|
+
input = { ...input, ctx: consumedCtx };
|
|
10485
|
+
sessionHolder.value = input;
|
|
10486
|
+
}
|
|
10321
10487
|
const opened = await openPluginSession(runHost, {
|
|
10322
10488
|
messages: input.messages || [],
|
|
10323
10489
|
resumed,
|
|
10490
|
+
pausedMidRun,
|
|
10324
10491
|
config
|
|
10325
10492
|
});
|
|
10326
10493
|
if (opened.denied) {
|
|
@@ -11110,7 +11277,7 @@ var HOOKS = {
|
|
|
11110
11277
|
sessionStart: { implemented: true },
|
|
11111
11278
|
userPromptSubmit: {
|
|
11112
11279
|
implemented: true,
|
|
11113
|
-
notes: "Fires once per run
|
|
11280
|
+
notes: "Fires once per run whenever the transcript tail is a NEW user turn \u2014 including a turn appended to a state that was restored from a snapshot. Skipped on a genuine resume, where the tail is a tool result rather than a user message."
|
|
11114
11281
|
},
|
|
11115
11282
|
preModelCall: {
|
|
11116
11283
|
implemented: true,
|
|
@@ -11123,7 +11290,7 @@ var HOOKS = {
|
|
|
11123
11290
|
},
|
|
11124
11291
|
postToolUse: {
|
|
11125
11292
|
implemented: true,
|
|
11126
|
-
notes: "Raw output, before compression and the hard cap. Also fires on a tool ERROR. Does NOT fire for a call that parks the run."
|
|
11293
|
+
notes: "Raw output, before compression and the hard cap. Also fires on a tool ERROR, on a preToolUse `result` short-circuit and on a tool-cache hit (`input.source` says which). Does NOT fire for a call that parks the run."
|
|
11127
11294
|
},
|
|
11128
11295
|
preCompact: { implemented: true },
|
|
11129
11296
|
postCompact: { implemented: true },
|
|
@@ -12107,6 +12274,12 @@ function auditLog(config = {}) {
|
|
|
12107
12274
|
return "[unserializable]";
|
|
12108
12275
|
}
|
|
12109
12276
|
};
|
|
12277
|
+
const renderDetail = (detail) => {
|
|
12278
|
+
if (!detail || typeof detail !== "object" || Array.isArray(detail) || !("args" in detail)) return detail;
|
|
12279
|
+
const { args, ...rest } = detail;
|
|
12280
|
+
const rendered = renderArgs(args);
|
|
12281
|
+
return rendered === void 0 ? rest : { ...rest, args: rendered };
|
|
12282
|
+
};
|
|
12110
12283
|
return {
|
|
12111
12284
|
name: config.name ?? "audit-log",
|
|
12112
12285
|
// Lowest number: runs first, so denials by later plugins are still recorded
|
|
@@ -12135,7 +12308,7 @@ function auditLog(config = {}) {
|
|
|
12135
12308
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12136
12309
|
kind: "notification",
|
|
12137
12310
|
status: kind,
|
|
12138
|
-
detail
|
|
12311
|
+
detail: renderDetail(detail)
|
|
12139
12312
|
});
|
|
12140
12313
|
},
|
|
12141
12314
|
sessionEnd: async ({ status }, ctx) => {
|