@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.d.cts
CHANGED
|
@@ -451,6 +451,13 @@ interface HookMap {
|
|
|
451
451
|
error?: Error;
|
|
452
452
|
durationMs: number;
|
|
453
453
|
executionId: string;
|
|
454
|
+
/**
|
|
455
|
+
* Where `output` came from. `tool` is a real execution (or its error);
|
|
456
|
+
* `hook` is a `preToolUse.result` short-circuit; `cache` is a hit on the
|
|
457
|
+
* tool's own result cache — that value already passed this hook once,
|
|
458
|
+
* when it was stored, so a redactor sees it twice.
|
|
459
|
+
*/
|
|
460
|
+
source?: "tool" | "hook" | "cache";
|
|
454
461
|
};
|
|
455
462
|
output: {
|
|
456
463
|
/** Rewritten output — redaction, normalization, schema coercion. */
|
|
@@ -1515,6 +1522,13 @@ type SummaryFactItem = {
|
|
|
1515
1522
|
source?: string;
|
|
1516
1523
|
};
|
|
1517
1524
|
type StructuredSummary = {
|
|
1525
|
+
/**
|
|
1526
|
+
* Standing instructions the user gave that still apply ("answer in Turkish",
|
|
1527
|
+
* "never restart prod"). Kept verbatim across summaries: a directive dropped
|
|
1528
|
+
* by a later summary counts as critical loss and is merged forward, unless
|
|
1529
|
+
* the user explicitly revoked it (listed in `discarded_obsolete`).
|
|
1530
|
+
*/
|
|
1531
|
+
user_directives?: string[];
|
|
1518
1532
|
stable_facts: SummaryFactItem[];
|
|
1519
1533
|
active_goals: string[];
|
|
1520
1534
|
open_questions: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -451,6 +451,13 @@ interface HookMap {
|
|
|
451
451
|
error?: Error;
|
|
452
452
|
durationMs: number;
|
|
453
453
|
executionId: string;
|
|
454
|
+
/**
|
|
455
|
+
* Where `output` came from. `tool` is a real execution (or its error);
|
|
456
|
+
* `hook` is a `preToolUse.result` short-circuit; `cache` is a hit on the
|
|
457
|
+
* tool's own result cache — that value already passed this hook once,
|
|
458
|
+
* when it was stored, so a redactor sees it twice.
|
|
459
|
+
*/
|
|
460
|
+
source?: "tool" | "hook" | "cache";
|
|
454
461
|
};
|
|
455
462
|
output: {
|
|
456
463
|
/** Rewritten output — redaction, normalization, schema coercion. */
|
|
@@ -1515,6 +1522,13 @@ type SummaryFactItem = {
|
|
|
1515
1522
|
source?: string;
|
|
1516
1523
|
};
|
|
1517
1524
|
type StructuredSummary = {
|
|
1525
|
+
/**
|
|
1526
|
+
* Standing instructions the user gave that still apply ("answer in Turkish",
|
|
1527
|
+
* "never restart prod"). Kept verbatim across summaries: a directive dropped
|
|
1528
|
+
* by a later summary counts as critical loss and is merged forward, unless
|
|
1529
|
+
* the user explicitly revoked it (listed in `discarded_obsolete`).
|
|
1530
|
+
*/
|
|
1531
|
+
user_directives?: string[];
|
|
1518
1532
|
stable_facts: SummaryFactItem[];
|
|
1519
1533
|
active_goals: string[];
|
|
1520
1534
|
open_questions: string[];
|
package/dist/index.mjs
CHANGED
|
@@ -4591,18 +4591,60 @@ function createToolsNode(initialTools, opts) {
|
|
|
4591
4591
|
toolStateRef.__currentToolCallId = toolCallId;
|
|
4592
4592
|
delete toolStateRef.__awaitingApproval;
|
|
4593
4593
|
}
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4594
|
+
const stableJson = (value) => {
|
|
4595
|
+
try {
|
|
4596
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
4597
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
4598
|
+
const keys = Object.keys(value).sort();
|
|
4599
|
+
return `{${keys.map((k) => JSON.stringify(k) + ":" + stableJson(value[k])).join(",")}}`;
|
|
4600
|
+
} catch {
|
|
4601
|
+
return String(value);
|
|
4602
|
+
}
|
|
4603
|
+
};
|
|
4604
|
+
const rejectByPolicy = (gateReason, gatedArgs) => {
|
|
4605
|
+
const reason = gateReason || `Tool call blocked by policy: ${toolName}`;
|
|
4606
|
+
push({ role: "tool", content: reason, tool_call_id: toolCallId, name: tc.name });
|
|
4607
|
+
toolHistory.push({
|
|
4608
|
+
executionId: nanoid(),
|
|
4609
|
+
toolName,
|
|
4610
|
+
args: gatedArgs,
|
|
4611
|
+
output: reason,
|
|
4612
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4613
|
+
tool_call_id: toolCallId,
|
|
4614
|
+
status: "rejected"
|
|
4615
|
+
});
|
|
4616
|
+
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args: gatedArgs, error: { message: reason } });
|
|
4617
|
+
recordTraceEvent(traceSession, {
|
|
4618
|
+
type: "tool_call",
|
|
4619
|
+
label: `Tool Denied - ${toolName}`,
|
|
4620
|
+
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
4621
|
+
status: "skipped",
|
|
4622
|
+
toolDetails,
|
|
4623
|
+
toolExecutionId: toolCallId,
|
|
4624
|
+
sections: buildToolTraceSections({
|
|
4625
|
+
args: gatedArgs,
|
|
4626
|
+
executionId: toolCallId,
|
|
4627
|
+
output: reason,
|
|
4628
|
+
status: "skipped",
|
|
4629
|
+
summary: reason,
|
|
4630
|
+
toolDetails,
|
|
4631
|
+
toolName
|
|
4632
|
+
})
|
|
4633
|
+
});
|
|
4634
|
+
toolCount += 1;
|
|
4635
|
+
return { status: "error" };
|
|
4636
|
+
};
|
|
4637
|
+
const applyPreToolUseGate = async (candidate) => {
|
|
4638
|
+
if (!host?.has("preToolUse")) return { ok: true, args: candidate, ask: false };
|
|
4598
4639
|
const gate = await host.runGate("preToolUse", {
|
|
4599
4640
|
toolName,
|
|
4600
4641
|
toolCallId,
|
|
4601
|
-
args,
|
|
4642
|
+
args: candidate,
|
|
4602
4643
|
tool,
|
|
4603
4644
|
executionCount: countSuccessfulToolExecutions(toolHistory, toolName)
|
|
4604
4645
|
});
|
|
4605
|
-
|
|
4646
|
+
let next = candidate;
|
|
4647
|
+
if (gate.input.args !== candidate) {
|
|
4606
4648
|
const revalidated = validateToolArgs(tool, gate.input.args);
|
|
4607
4649
|
if (!revalidated.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
4608
4650
|
const message = `Tool argument validation failed after a plugin rewrite for ${toolName}: ${revalidated.message}`;
|
|
@@ -4610,48 +4652,31 @@ function createToolsNode(initialTools, opts) {
|
|
|
4610
4652
|
markToolFailure(message, toolName, toolCallId);
|
|
4611
4653
|
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args: gate.input.args, error: { message } });
|
|
4612
4654
|
toolCount += 1;
|
|
4613
|
-
return { status: "error" };
|
|
4655
|
+
return { ok: false, outcome: { status: "error" } };
|
|
4614
4656
|
}
|
|
4615
|
-
|
|
4657
|
+
next = revalidated.ok ? revalidated.value : gate.input.args;
|
|
4616
4658
|
}
|
|
4617
4659
|
if (gate.decision === "deny") {
|
|
4618
|
-
|
|
4619
|
-
push({ role: "tool", content: reason, tool_call_id: toolCallId, name: tc.name });
|
|
4620
|
-
toolHistory.push({
|
|
4621
|
-
executionId: nanoid(),
|
|
4622
|
-
toolName,
|
|
4623
|
-
args,
|
|
4624
|
-
output: reason,
|
|
4625
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4626
|
-
tool_call_id: toolCallId,
|
|
4627
|
-
status: "rejected"
|
|
4628
|
-
});
|
|
4629
|
-
onEvent?.({ type: "tool_call", phase: "skipped", name: toolName, id: tc.id, args, error: { message: reason } });
|
|
4630
|
-
recordTraceEvent(traceSession, {
|
|
4631
|
-
type: "tool_call",
|
|
4632
|
-
label: `Tool Denied - ${toolName}`,
|
|
4633
|
-
actor: { scope: "tool", name: toolName, role: "tool" },
|
|
4634
|
-
status: "skipped",
|
|
4635
|
-
toolDetails,
|
|
4636
|
-
toolExecutionId: toolCallId,
|
|
4637
|
-
sections: buildToolTraceSections({
|
|
4638
|
-
args,
|
|
4639
|
-
executionId: toolCallId,
|
|
4640
|
-
output: reason,
|
|
4641
|
-
status: "skipped",
|
|
4642
|
-
summary: reason,
|
|
4643
|
-
toolDetails,
|
|
4644
|
-
toolName
|
|
4645
|
-
})
|
|
4646
|
-
});
|
|
4647
|
-
toolCount += 1;
|
|
4648
|
-
return { status: "error" };
|
|
4649
|
-
}
|
|
4650
|
-
if (gate.decision === "ask") {
|
|
4651
|
-
hookAskedForApproval = true;
|
|
4652
|
-
hookApprovalPrompt = gate.approvalPrompt;
|
|
4660
|
+
return { ok: false, outcome: rejectByPolicy(gate.reason, next) };
|
|
4653
4661
|
}
|
|
4654
|
-
|
|
4662
|
+
return {
|
|
4663
|
+
ok: true,
|
|
4664
|
+
args: next,
|
|
4665
|
+
ask: gate.decision === "ask",
|
|
4666
|
+
approvalPrompt: gate.approvalPrompt,
|
|
4667
|
+
result: gate.shortCircuit
|
|
4668
|
+
};
|
|
4669
|
+
};
|
|
4670
|
+
let hookAskedForApproval = false;
|
|
4671
|
+
let hookApprovalPrompt;
|
|
4672
|
+
let hookResult;
|
|
4673
|
+
{
|
|
4674
|
+
const gated = await applyPreToolUseGate(args);
|
|
4675
|
+
if (!gated.ok) return gated.outcome;
|
|
4676
|
+
args = gated.args;
|
|
4677
|
+
hookAskedForApproval = gated.ask;
|
|
4678
|
+
hookApprovalPrompt = gated.approvalPrompt;
|
|
4679
|
+
if (gated.result !== void 0) hookResult = gated.result;
|
|
4655
4680
|
}
|
|
4656
4681
|
const maxExecutionsPerRun = normalizeMaxExecutionsPerRun(tool.maxExecutionsPerRun);
|
|
4657
4682
|
if (maxExecutionsPerRun !== null) {
|
|
@@ -4736,7 +4761,34 @@ function createToolsNode(initialTools, opts) {
|
|
|
4736
4761
|
return { status: "rejected", approval: approvalEntry };
|
|
4737
4762
|
}
|
|
4738
4763
|
if (approvalEntry.status === "approved" && approvalEntry.approvedArgs !== void 0) {
|
|
4739
|
-
|
|
4764
|
+
const approvedArgs = approvalEntry.approvedArgs;
|
|
4765
|
+
if (stableJson(approvedArgs) !== stableJson(args)) {
|
|
4766
|
+
const settleRefused = (resolution) => {
|
|
4767
|
+
approvalEntry.metadata = { ...approvalEntry.metadata || {}, resolution };
|
|
4768
|
+
approvalEntry.status = "executed";
|
|
4769
|
+
approvalEntry.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4770
|
+
pendingByCallId.set(toolCallId, approvalEntry);
|
|
4771
|
+
};
|
|
4772
|
+
const revalidated = validateToolArgs(tool, approvedArgs);
|
|
4773
|
+
if (!revalidated.ok && resolved.toolResponses.schemaValidation === "strict") {
|
|
4774
|
+
const message = `Tool argument validation failed for reviewer-edited arguments of ${toolName}: ${revalidated.message}`;
|
|
4775
|
+
push({ role: "tool", content: message, tool_call_id: toolCallId, name: tc.name });
|
|
4776
|
+
markToolFailure(message, toolName, toolCallId);
|
|
4777
|
+
settleRefused("invalid_args");
|
|
4778
|
+
onEvent?.({ type: "tool_call", phase: "error", name: toolName, id: tc.id, args: approvedArgs, error: { message } });
|
|
4779
|
+
toolCount += 1;
|
|
4780
|
+
return { status: "error", approval: approvalEntry };
|
|
4781
|
+
}
|
|
4782
|
+
const gated = await applyPreToolUseGate(revalidated.ok ? revalidated.value : approvedArgs);
|
|
4783
|
+
if (!gated.ok) {
|
|
4784
|
+
settleRefused("policy_denied");
|
|
4785
|
+
return { ...gated.outcome, approval: approvalEntry };
|
|
4786
|
+
}
|
|
4787
|
+
args = gated.args;
|
|
4788
|
+
if (gated.result !== void 0 && hookResult === void 0) hookResult = gated.result;
|
|
4789
|
+
} else {
|
|
4790
|
+
args = approvedArgs;
|
|
4791
|
+
}
|
|
4740
4792
|
onEvent?.({ type: "tool_approval", status: "approved", id: approvalEntry.id, toolName: approvalEntry.toolName, toolCallId: approvalEntry.toolCallId, decidedBy: approvalEntry.decidedBy, comment: approvalEntry.comment });
|
|
4741
4793
|
}
|
|
4742
4794
|
}
|
|
@@ -4746,16 +4798,6 @@ function createToolsNode(initialTools, opts) {
|
|
|
4746
4798
|
const cacheCfg = tool.cache;
|
|
4747
4799
|
const cacheEnabled = cacheCfg === true || cacheCfg && typeof cacheCfg === "object";
|
|
4748
4800
|
const cacheKeyFn = cacheEnabled && typeof cacheCfg === "object" ? cacheCfg.keyFn : void 0;
|
|
4749
|
-
const stableJson = (value) => {
|
|
4750
|
-
try {
|
|
4751
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
4752
|
-
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
4753
|
-
const keys = Object.keys(value).sort();
|
|
4754
|
-
return `{${keys.map((k) => JSON.stringify(k) + ":" + stableJson(value[k])).join(",")}}`;
|
|
4755
|
-
} catch {
|
|
4756
|
-
return String(value);
|
|
4757
|
-
}
|
|
4758
|
-
};
|
|
4759
4801
|
const cacheKey = cacheEnabled ? `${toolName}|${cacheKeyFn ? cacheKeyFn(args) : stableJson(args)}` : void 0;
|
|
4760
4802
|
const toolCache = state.toolCache = state.toolCache || {};
|
|
4761
4803
|
let cachedHit = void 0;
|
|
@@ -4803,6 +4845,22 @@ function createToolsNode(initialTools, opts) {
|
|
|
4803
4845
|
try {
|
|
4804
4846
|
if (cachedHit !== void 0) {
|
|
4805
4847
|
const executionId2 = nanoid();
|
|
4848
|
+
if (host?.has("postToolUse")) {
|
|
4849
|
+
const shortGate = await host.runGate("postToolUse", {
|
|
4850
|
+
toolName,
|
|
4851
|
+
toolCallId,
|
|
4852
|
+
args,
|
|
4853
|
+
output: cachedHit,
|
|
4854
|
+
durationMs: 0,
|
|
4855
|
+
executionId: executionId2,
|
|
4856
|
+
source: shortCircuitSource === "plugin" ? "hook" : "cache"
|
|
4857
|
+
});
|
|
4858
|
+
if (shortGate.decision === "deny") {
|
|
4859
|
+
cachedHit = shortGate.reason || `Tool output withheld by policy: ${toolName}`;
|
|
4860
|
+
} else if (shortGate.input.output !== cachedHit) {
|
|
4861
|
+
cachedHit = shortGate.input.output;
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4806
4864
|
const { output: compressedCachedHit, stats: cachedContextPilotStats } = maybeCompressToolOutput(toolName, cachedHit, executionId2);
|
|
4807
4865
|
const responsePolicy2 = applyToolResponseHardCap(toolName, compressedCachedHit, executionId2, resolved);
|
|
4808
4866
|
toolHistory.push({
|
|
@@ -4969,7 +5027,8 @@ function createToolsNode(initialTools, opts) {
|
|
|
4969
5027
|
args,
|
|
4970
5028
|
output,
|
|
4971
5029
|
durationMs,
|
|
4972
|
-
executionId
|
|
5030
|
+
executionId,
|
|
5031
|
+
source: "tool"
|
|
4973
5032
|
});
|
|
4974
5033
|
if (postGate.decision === "deny") {
|
|
4975
5034
|
output = postGate.reason || `Tool output withheld by policy: ${toolName}`;
|
|
@@ -5064,7 +5123,8 @@ function createToolsNode(initialTools, opts) {
|
|
|
5064
5123
|
output: message,
|
|
5065
5124
|
error: error instanceof Error ? error : new Error(message),
|
|
5066
5125
|
durationMs,
|
|
5067
|
-
executionId
|
|
5126
|
+
executionId,
|
|
5127
|
+
source: "tool"
|
|
5068
5128
|
});
|
|
5069
5129
|
if (errorGate.decision === "deny") {
|
|
5070
5130
|
message = errorGate.reason || `Tool error withheld by policy: ${toolName}`;
|
|
@@ -5740,7 +5800,14 @@ var DISALLOWED_CTX_KEYS = /* @__PURE__ */ new Set([
|
|
|
5740
5800
|
// Session bookkeeping: a resumed run must open its OWN session, so these
|
|
5741
5801
|
// must not travel in a snapshot.
|
|
5742
5802
|
"__pluginRunStarted",
|
|
5743
|
-
"__pluginRunEnded"
|
|
5803
|
+
"__pluginRunEnded",
|
|
5804
|
+
// Set by restoreSnapshot on the way OUT, consumed by the next invoke. Never
|
|
5805
|
+
// persisted: a snapshot of a restored run would otherwise mark every later
|
|
5806
|
+
// restore as "resumed" forever.
|
|
5807
|
+
"__restoredFromSnapshot",
|
|
5808
|
+
// A per-turn verdict. It describes the run that produced the snapshot, not
|
|
5809
|
+
// the run that will resume from it.
|
|
5810
|
+
"__guardrailBlocked"
|
|
5744
5811
|
]);
|
|
5745
5812
|
var clone = (value) => {
|
|
5746
5813
|
if (typeof globalThis.structuredClone === "function") {
|
|
@@ -6079,6 +6146,9 @@ function isSyntheticSummaryMessage(message) {
|
|
|
6079
6146
|
}
|
|
6080
6147
|
return false;
|
|
6081
6148
|
}
|
|
6149
|
+
function isCompactedToolContent(content) {
|
|
6150
|
+
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"));
|
|
6151
|
+
}
|
|
6082
6152
|
|
|
6083
6153
|
// src/structuredOutput/manager.ts
|
|
6084
6154
|
var StructuredOutputManager = class {
|
|
@@ -6364,6 +6434,7 @@ var SLOT_NAMES = [
|
|
|
6364
6434
|
"contextBuilder"
|
|
6365
6435
|
];
|
|
6366
6436
|
var DECISION_RANK = { allow: 0, ask: 1, deny: 2 };
|
|
6437
|
+
var isHookDecision = (value) => typeof value === "string" && Object.prototype.hasOwnProperty.call(DECISION_RANK, value);
|
|
6367
6438
|
function escalate(current, next) {
|
|
6368
6439
|
return DECISION_RANK[next] > DECISION_RANK[current] ? next : current;
|
|
6369
6440
|
}
|
|
@@ -6373,6 +6444,15 @@ var HookTimeoutError = class extends Error {
|
|
|
6373
6444
|
this.name = "HookTimeoutError";
|
|
6374
6445
|
}
|
|
6375
6446
|
};
|
|
6447
|
+
var InvalidHookDecisionError = class extends Error {
|
|
6448
|
+
constructor(plugin, hook, value) {
|
|
6449
|
+
const shown = typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
6450
|
+
super(
|
|
6451
|
+
`plugin "${plugin}" hook "${hook}" returned an unknown decision ${shown}; expected "allow", "ask" or "deny"`
|
|
6452
|
+
);
|
|
6453
|
+
this.name = "InvalidHookDecisionError";
|
|
6454
|
+
}
|
|
6455
|
+
};
|
|
6376
6456
|
function withTimeout(value, ms, label) {
|
|
6377
6457
|
if (!ms || !Number.isFinite(ms) || ms <= 0) return Promise.resolve(value);
|
|
6378
6458
|
return new Promise((resolve, reject) => {
|
|
@@ -6662,10 +6742,18 @@ function createPluginHost(plugins = [], options = {}) {
|
|
|
6662
6742
|
`plugin "${entry.plugin}" hook "${hook}"`
|
|
6663
6743
|
);
|
|
6664
6744
|
output = raw ?? void 0;
|
|
6745
|
+
if (output && typeof output === "object" && output.decision !== void 0 && !isHookDecision(output.decision)) {
|
|
6746
|
+
throw new InvalidHookDecisionError(entry.plugin, hook, output.decision);
|
|
6747
|
+
}
|
|
6665
6748
|
} catch (err) {
|
|
6666
6749
|
const message = err instanceof Error ? err.message : String(err);
|
|
6667
6750
|
const timedOut = err instanceof HookTimeoutError;
|
|
6668
|
-
|
|
6751
|
+
const badDecision = err instanceof InvalidHookDecisionError;
|
|
6752
|
+
if (badDecision) {
|
|
6753
|
+
logger.error(`plugin "${entry.plugin}" hook "${hook}" returned an invalid decision:`, message);
|
|
6754
|
+
} else {
|
|
6755
|
+
logger.warn(`plugin "${entry.plugin}" hook "${hook}" failed:`, message);
|
|
6756
|
+
}
|
|
6669
6757
|
emitPluginEvent(entry, hook, {
|
|
6670
6758
|
phase: timedOut ? "timeout" : "error",
|
|
6671
6759
|
error: { message },
|
|
@@ -6677,8 +6765,9 @@ function createPluginHost(plugins = [], options = {}) {
|
|
|
6677
6765
|
durationMs: performanceNow() - startedAt
|
|
6678
6766
|
});
|
|
6679
6767
|
if (entry.failureMode === "closed") {
|
|
6768
|
+
const cause = timedOut ? "timed out" : badDecision ? "invalid decision" : "hook error";
|
|
6680
6769
|
result.decision = "deny";
|
|
6681
|
-
result.reason = `${entry.plugin}: ${
|
|
6770
|
+
result.reason = `${entry.plugin}: ${cause} (fail-closed) \u2014 ${message}`;
|
|
6682
6771
|
result.deniedBy = entry.plugin;
|
|
6683
6772
|
result.input = current;
|
|
6684
6773
|
return result;
|
|
@@ -6736,11 +6825,19 @@ function createPluginHost(plugins = [], options = {}) {
|
|
|
6736
6825
|
if (output.metadata && typeof output.metadata === "object") {
|
|
6737
6826
|
result.metadata = { ...result.metadata ?? {}, ...output.metadata };
|
|
6738
6827
|
}
|
|
6739
|
-
|
|
6828
|
+
let decision = output.decision;
|
|
6829
|
+
if (decision === "ask" && hook !== "preToolUse") {
|
|
6830
|
+
logger.warn(
|
|
6831
|
+
`plugin "${entry.plugin}" returned "ask" from "${hook}", which cannot pause for approval; treating it as "deny".`
|
|
6832
|
+
);
|
|
6833
|
+
decision = "deny";
|
|
6834
|
+
}
|
|
6740
6835
|
const escalated = decision ? escalate(result.decision, decision) : result.decision;
|
|
6741
6836
|
const escalatesHere = escalated !== result.decision;
|
|
6742
6837
|
result.decision = escalated;
|
|
6743
|
-
if (
|
|
6838
|
+
if (escalatesHere) {
|
|
6839
|
+
result.reason = typeof output.reason === "string" ? `${entry.plugin}: ${output.reason}` : void 0;
|
|
6840
|
+
} else if (typeof output.reason === "string" && !result.reason) {
|
|
6744
6841
|
result.reason = `${entry.plugin}: ${output.reason}`;
|
|
6745
6842
|
}
|
|
6746
6843
|
if (mutatedHere) {
|
|
@@ -6834,7 +6931,7 @@ async function openPluginSession(runHost, params) {
|
|
|
6834
6931
|
const appended = startGate.collected.systemPromptAppend ?? [];
|
|
6835
6932
|
const systemPromptAppend = appended.length > 0 ? appended.join("\n\n") : void 0;
|
|
6836
6933
|
const last = messages[messages.length - 1];
|
|
6837
|
-
if (params.
|
|
6934
|
+
if (params.pausedMidRun || last?.role !== "user" || !runHost.has("userPromptSubmit")) {
|
|
6838
6935
|
return { messages, systemPromptAppend };
|
|
6839
6936
|
}
|
|
6840
6937
|
const content = last.content;
|
|
@@ -7079,6 +7176,11 @@ function createAgent(opts) {
|
|
|
7079
7176
|
delete nextCtx.__paused;
|
|
7080
7177
|
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
7081
7178
|
}
|
|
7179
|
+
if (state.ctx?.__restoredFromSnapshot) {
|
|
7180
|
+
const nextCtx = { ...state.ctx };
|
|
7181
|
+
delete nextCtx.__restoredFromSnapshot;
|
|
7182
|
+
state = { ...state, ctx: Object.keys(nextCtx).length > 0 ? nextCtx : void 0 };
|
|
7183
|
+
}
|
|
7082
7184
|
let resumeStage = null;
|
|
7083
7185
|
if (state.ctx?.__resumeStage) {
|
|
7084
7186
|
const nextCtx = { ...state.ctx };
|
|
@@ -7353,7 +7455,7 @@ function createAgent(opts) {
|
|
|
7353
7455
|
continue;
|
|
7354
7456
|
}
|
|
7355
7457
|
if (toolCalls.length === 0) {
|
|
7356
|
-
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize) {
|
|
7458
|
+
if (soManager && !state.ctx?.__finalizedDueToStructuredOutput && !state.ctx?.__structuredOutputForceFinalize && !state.ctx?.__guardrailBlocked) {
|
|
7357
7459
|
if (soManager.strategy.kind === "native") {
|
|
7358
7460
|
const assistantText = extractMessageText2(lastMsg);
|
|
7359
7461
|
if (assistantText) {
|
|
@@ -7522,6 +7624,7 @@ function createAgent(opts) {
|
|
|
7522
7624
|
delete ctx.__plugins;
|
|
7523
7625
|
delete ctx.__applySystemPromptContribution;
|
|
7524
7626
|
delete ctx.__pluginState;
|
|
7627
|
+
delete ctx.__guardrailBlocked;
|
|
7525
7628
|
const inheritedSession = config?.__pluginSession;
|
|
7526
7629
|
ctx.__runId = inheritedSession?.runId ?? traceSession?.sessionId ?? `run_${nanoid(10)}`;
|
|
7527
7630
|
const stateHolder = inheritedSession?.stateHolder ?? { value: void 0 };
|
|
@@ -7571,9 +7674,11 @@ function createAgent(opts) {
|
|
|
7571
7674
|
usage: input.usage || { perRequest: [], totals: {} }
|
|
7572
7675
|
};
|
|
7573
7676
|
stateHolder.value = initial;
|
|
7574
|
-
const
|
|
7575
|
-
input.ctx?.
|
|
7677
|
+
const pausedMidRun = Boolean(
|
|
7678
|
+
input.ctx?.__paused || input.ctx?.__resumeStage || input.ctx?.__awaitingApproval || input.ctx?.__awaitingUserQuestion
|
|
7576
7679
|
);
|
|
7680
|
+
const resumed = Boolean(input.ctx?.__restoredFromSnapshot || pausedMidRun);
|
|
7681
|
+
delete ctx.__restoredFromSnapshot;
|
|
7577
7682
|
const startedAt = Date.now();
|
|
7578
7683
|
let sessionEnded = false;
|
|
7579
7684
|
const endSession = async (status, payload) => {
|
|
@@ -7599,6 +7704,7 @@ function createAgent(opts) {
|
|
|
7599
7704
|
const opened = await openPluginSession(runHost, {
|
|
7600
7705
|
messages: initial.messages,
|
|
7601
7706
|
resumed,
|
|
7707
|
+
pausedMidRun,
|
|
7602
7708
|
config
|
|
7603
7709
|
});
|
|
7604
7710
|
if (opened.messages !== initial.messages) {
|
|
@@ -7959,6 +8065,10 @@ function collectRecentTurns(messages, lastTurnsToKeep) {
|
|
|
7959
8065
|
function renderStructuredSummary(summary) {
|
|
7960
8066
|
if (!summary) return "";
|
|
7961
8067
|
const lines = ["Context summary:"];
|
|
8068
|
+
if (summary.user_directives && summary.user_directives.length > 0) {
|
|
8069
|
+
lines.push("User directives (still in force \u2014 follow them):");
|
|
8070
|
+
lines.push(...summary.user_directives.map((directive) => `- ${directive}`));
|
|
8071
|
+
}
|
|
7962
8072
|
if (summary.stable_facts.length > 0) {
|
|
7963
8073
|
lines.push("Stable facts:");
|
|
7964
8074
|
lines.push(...summary.stable_facts.map((fact) => `- ${fact.key}: ${fact.value}`));
|
|
@@ -8034,17 +8144,46 @@ function clampToBudget(messages, maxContextTokens) {
|
|
|
8034
8144
|
}
|
|
8035
8145
|
return working;
|
|
8036
8146
|
}
|
|
8147
|
+
function collectSummaryOnlyBody(body, hasSummary, lastTurnsToKeep) {
|
|
8148
|
+
if (!hasSummary) {
|
|
8149
|
+
return collectRecentTurns(body, Math.max(1, lastTurnsToKeep));
|
|
8150
|
+
}
|
|
8151
|
+
let boundary = -1;
|
|
8152
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
8153
|
+
if (isSyntheticSummaryMessage(body[index])) {
|
|
8154
|
+
boundary = index;
|
|
8155
|
+
break;
|
|
8156
|
+
}
|
|
8157
|
+
}
|
|
8158
|
+
let lastUserIndex = -1;
|
|
8159
|
+
for (let index = body.length - 1; index >= 0; index -= 1) {
|
|
8160
|
+
if (body[index].role === "user") {
|
|
8161
|
+
lastUserIndex = index;
|
|
8162
|
+
break;
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8165
|
+
const keep = /* @__PURE__ */ new Set();
|
|
8166
|
+
const firstUserIndex = body.findIndex((message) => message.role === "user");
|
|
8167
|
+
if (firstUserIndex >= 0) keep.add(firstUserIndex);
|
|
8168
|
+
const verbatimFrom = Math.min(lastUserIndex >= 0 ? lastUserIndex : body.length, boundary + 1);
|
|
8169
|
+
for (let index = Math.max(0, verbatimFrom); index < body.length; index += 1) {
|
|
8170
|
+
if (!isSyntheticSummaryMessage(body[index])) keep.add(index);
|
|
8171
|
+
}
|
|
8172
|
+
return body.filter((_, index) => keep.has(index));
|
|
8173
|
+
}
|
|
8037
8174
|
function buildModelMessages(state, config) {
|
|
8038
8175
|
const rawMessages = state.messages || [];
|
|
8039
8176
|
if (config.context.policy === "raw") {
|
|
8040
8177
|
return clampToBudget(rawMessages, config.limits.maxContextTokens);
|
|
8041
8178
|
}
|
|
8042
|
-
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
8043
8179
|
const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
|
|
8044
8180
|
const summaryText = renderStructuredSummary(latestSummary);
|
|
8045
8181
|
const memoryText = renderMemoryBlock(state.memoryFacts);
|
|
8046
|
-
const systemMessage2 =
|
|
8047
|
-
const body = systemMessage2 ?
|
|
8182
|
+
const systemMessage2 = rawMessages[0]?.role === "system" ? rawMessages[0] : void 0;
|
|
8183
|
+
const body = config.context.policy === "summary_only" ? collectSummaryOnlyBody(systemMessage2 ? rawMessages.slice(1) : rawMessages, Boolean(summaryText), config.context.lastTurnsToKeep) : (() => {
|
|
8184
|
+
const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
|
|
8185
|
+
return recentMessages[0]?.role === "system" ? recentMessages.slice(1) : recentMessages;
|
|
8186
|
+
})();
|
|
8048
8187
|
const syntheticContextMessages = [];
|
|
8049
8188
|
if (summaryText) {
|
|
8050
8189
|
syntheticContextMessages.push({ role: "system", name: "context_summary", content: summaryText });
|
|
@@ -8055,7 +8194,7 @@ function buildModelMessages(state, config) {
|
|
|
8055
8194
|
const assembled = [
|
|
8056
8195
|
...systemMessage2 ? [systemMessage2] : [],
|
|
8057
8196
|
...syntheticContextMessages,
|
|
8058
|
-
...
|
|
8197
|
+
...body
|
|
8059
8198
|
];
|
|
8060
8199
|
return clampToBudget(assembled, config.limits.maxContextTokens);
|
|
8061
8200
|
}
|
|
@@ -8100,6 +8239,7 @@ function extractJsonObject(text) {
|
|
|
8100
8239
|
}
|
|
8101
8240
|
function normalizeStructuredSummary(input, fallbackText) {
|
|
8102
8241
|
const summary = {
|
|
8242
|
+
user_directives: Array.isArray(input?.user_directives) ? input.user_directives.map((directive) => String(directive ?? "").trim()).filter(Boolean) : [],
|
|
8103
8243
|
stable_facts: Array.isArray(input?.stable_facts) ? input.stable_facts.filter((fact) => fact && typeof fact === "object").map((fact) => ({
|
|
8104
8244
|
key: String(fact.key || "unknown"),
|
|
8105
8245
|
value: String(fact.value || ""),
|
|
@@ -8160,7 +8300,7 @@ function extractCanonicalFacts(messages) {
|
|
|
8160
8300
|
return [...facts.values()];
|
|
8161
8301
|
}
|
|
8162
8302
|
function isSummarizedToolPlaceholder(content) {
|
|
8163
|
-
return
|
|
8303
|
+
return isCompactedToolContent(content);
|
|
8164
8304
|
}
|
|
8165
8305
|
function collectLatestTurnToolCallIds(messages) {
|
|
8166
8306
|
const ids = /* @__PURE__ */ new Set();
|
|
@@ -8219,11 +8359,16 @@ function runIntegrityCheck(previous, current) {
|
|
|
8219
8359
|
const previousKeys = new Set((previous?.stable_facts || []).map((fact) => fact.key));
|
|
8220
8360
|
const currentKeys = new Set(current.stable_facts.map((fact) => fact.key));
|
|
8221
8361
|
const obsoleteKeys = new Set(current.discarded_obsolete);
|
|
8222
|
-
const
|
|
8362
|
+
const currentDirectives = new Set(current.user_directives || []);
|
|
8363
|
+
const lostDirectives = (previous?.user_directives || []).filter((directive) => !currentDirectives.has(directive) && !obsoleteKeys.has(directive));
|
|
8364
|
+
const criticalFactLoss = [...previousKeys].some((key) => !obsoleteKeys.has(key) && !currentKeys.has(key)) || lostDirectives.length > 0;
|
|
8223
8365
|
const obsoleteFactRevived = current.stable_facts.some((fact) => obsoleteKeys.has(fact.key));
|
|
8224
8366
|
if (criticalFactLoss) {
|
|
8225
8367
|
notes.push("Missing previously retained facts; merged forward during integrity repair.");
|
|
8226
8368
|
}
|
|
8369
|
+
if (lostDirectives.length > 0) {
|
|
8370
|
+
notes.push(`Dropped ${lostDirectives.length} user directive(s); merged forward during integrity repair.`);
|
|
8371
|
+
}
|
|
8227
8372
|
if (obsoleteFactRevived) {
|
|
8228
8373
|
notes.push("Obsolete facts reappeared; removed during integrity repair.");
|
|
8229
8374
|
}
|
|
@@ -8246,7 +8391,13 @@ function repairStructuredSummary(previous, current, integrity) {
|
|
|
8246
8391
|
}
|
|
8247
8392
|
}
|
|
8248
8393
|
const filteredFacts = integrity.obsoleteFactRevived ? nextFacts.filter((fact) => !current.discarded_obsolete.includes(fact.key)) : nextFacts;
|
|
8249
|
-
|
|
8394
|
+
const directives = [...current.user_directives || []];
|
|
8395
|
+
for (const directive of previous.user_directives || []) {
|
|
8396
|
+
if (!directives.includes(directive) && !current.discarded_obsolete.includes(directive)) {
|
|
8397
|
+
directives.push(directive);
|
|
8398
|
+
}
|
|
8399
|
+
}
|
|
8400
|
+
return { ...current, user_directives: directives, stable_facts: filteredFacts };
|
|
8250
8401
|
}
|
|
8251
8402
|
function createContextSummarizeNode(opts) {
|
|
8252
8403
|
return async (state) => {
|
|
@@ -8320,6 +8471,7 @@ function createContextSummarizeNode(opts) {
|
|
|
8320
8471
|
|
|
8321
8472
|
Return exactly one JSON object with this schema:
|
|
8322
8473
|
{
|
|
8474
|
+
"user_directives": [string],
|
|
8323
8475
|
"stable_facts": [{ "key": string, "value": string, "confidence": number }],
|
|
8324
8476
|
"active_goals": [string],
|
|
8325
8477
|
"open_questions": [string],
|
|
@@ -8328,6 +8480,7 @@ Return exactly one JSON object with this schema:
|
|
|
8328
8480
|
}
|
|
8329
8481
|
|
|
8330
8482
|
Rules:
|
|
8483
|
+
- 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.
|
|
8331
8484
|
- Keep stable_facts only for facts that future turns must remember.
|
|
8332
8485
|
- Put invalidated or superseded fact keys in discarded_obsolete.
|
|
8333
8486
|
- Preserve active_goals still relevant to the user.
|
|
@@ -8395,6 +8548,7 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
|
|
|
8395
8548
|
durationMs = Date.now() - startTime;
|
|
8396
8549
|
structuredSummary = normalizeStructuredSummary(void 0, previousSummary || conversationText.slice(0, 800));
|
|
8397
8550
|
structuredSummary = mergeStableFacts(structuredSummary, canonicalFacts);
|
|
8551
|
+
structuredSummary.user_directives = [...previousStructuredSummary?.user_directives || []];
|
|
8398
8552
|
integrity = { passed: true, criticalFactLoss: false, obsoleteFactRevived: false, notes: ["Fallback summary generated locally."] };
|
|
8399
8553
|
summaryText = renderStructuredSummary(structuredSummary);
|
|
8400
8554
|
const errorEvent = {
|
|
@@ -10255,6 +10409,11 @@ ${pluginAppend}`;
|
|
|
10255
10409
|
const instance = {
|
|
10256
10410
|
invoke: async (input, config) => {
|
|
10257
10411
|
await ensurePluginSetup();
|
|
10412
|
+
if (input?.ctx?.__guardrailBlocked !== void 0) {
|
|
10413
|
+
const clearedCtx = { ...input.ctx };
|
|
10414
|
+
delete clearedCtx.__guardrailBlocked;
|
|
10415
|
+
input = { ...input, ctx: clearedCtx };
|
|
10416
|
+
}
|
|
10258
10417
|
const sessionHolder = { value: input };
|
|
10259
10418
|
const emitSessionEvent = (event) => {
|
|
10260
10419
|
try {
|
|
@@ -10305,12 +10464,20 @@ ${pluginAppend}`;
|
|
|
10305
10464
|
stateHolder: sessionHolder
|
|
10306
10465
|
}
|
|
10307
10466
|
};
|
|
10308
|
-
const
|
|
10309
|
-
input.ctx?.
|
|
10467
|
+
const pausedMidRun = Boolean(
|
|
10468
|
+
input.ctx?.__paused || input.ctx?.__resumeStage || input.ctx?.__awaitingApproval || input.ctx?.__awaitingUserQuestion
|
|
10310
10469
|
);
|
|
10470
|
+
const resumed = Boolean(input.ctx?.__restoredFromSnapshot || pausedMidRun);
|
|
10471
|
+
if (input.ctx?.__restoredFromSnapshot) {
|
|
10472
|
+
const consumedCtx = { ...input.ctx };
|
|
10473
|
+
delete consumedCtx.__restoredFromSnapshot;
|
|
10474
|
+
input = { ...input, ctx: consumedCtx };
|
|
10475
|
+
sessionHolder.value = input;
|
|
10476
|
+
}
|
|
10311
10477
|
const opened = await openPluginSession(runHost, {
|
|
10312
10478
|
messages: input.messages || [],
|
|
10313
10479
|
resumed,
|
|
10480
|
+
pausedMidRun,
|
|
10314
10481
|
config
|
|
10315
10482
|
});
|
|
10316
10483
|
if (opened.denied) {
|
|
@@ -11100,7 +11267,7 @@ var HOOKS = {
|
|
|
11100
11267
|
sessionStart: { implemented: true },
|
|
11101
11268
|
userPromptSubmit: {
|
|
11102
11269
|
implemented: true,
|
|
11103
|
-
notes: "Fires once per run
|
|
11270
|
+
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."
|
|
11104
11271
|
},
|
|
11105
11272
|
preModelCall: {
|
|
11106
11273
|
implemented: true,
|
|
@@ -11113,7 +11280,7 @@ var HOOKS = {
|
|
|
11113
11280
|
},
|
|
11114
11281
|
postToolUse: {
|
|
11115
11282
|
implemented: true,
|
|
11116
|
-
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."
|
|
11283
|
+
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."
|
|
11117
11284
|
},
|
|
11118
11285
|
preCompact: { implemented: true },
|
|
11119
11286
|
postCompact: { implemented: true },
|
|
@@ -12097,6 +12264,12 @@ function auditLog(config = {}) {
|
|
|
12097
12264
|
return "[unserializable]";
|
|
12098
12265
|
}
|
|
12099
12266
|
};
|
|
12267
|
+
const renderDetail = (detail) => {
|
|
12268
|
+
if (!detail || typeof detail !== "object" || Array.isArray(detail) || !("args" in detail)) return detail;
|
|
12269
|
+
const { args, ...rest } = detail;
|
|
12270
|
+
const rendered = renderArgs(args);
|
|
12271
|
+
return rendered === void 0 ? rest : { ...rest, args: rendered };
|
|
12272
|
+
};
|
|
12100
12273
|
return {
|
|
12101
12274
|
name: config.name ?? "audit-log",
|
|
12102
12275
|
// Lowest number: runs first, so denials by later plugins are still recorded
|
|
@@ -12125,7 +12298,7 @@ function auditLog(config = {}) {
|
|
|
12125
12298
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12126
12299
|
kind: "notification",
|
|
12127
12300
|
status: kind,
|
|
12128
|
-
detail
|
|
12301
|
+
detail: renderDetail(detail)
|
|
12129
12302
|
});
|
|
12130
12303
|
},
|
|
12131
12304
|
sessionEnd: async ({ status }, ctx) => {
|