@sema-agent/core 5.4.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/dist/core/arg-summary.d.ts +8 -0
- package/dist/core/arg-summary.js +27 -9
- package/dist/core/runner/prepare-task.js +2 -2
- package/dist/core/runner/tool-disclosure.js +29 -2
- package/dist/core/secret-env.d.ts +9 -1
- package/dist/core/secret-env.js +20 -3
- package/dist/core/untrusted-egress.d.ts +2 -1
- package/dist/core/untrusted-egress.js +5 -3
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/tools/scheduler-tools.js +48 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 5.5.0 (2026-08-03)
|
|
4
|
+
|
|
5
|
+
_Three follow-through items: the deferred-placeholder wrong-guess round trip halves, durable cron jobs survive their creating session, and the redaction findings channel closes its last three audit gaps._
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Deferred-tool placeholders answer a wrong guess with the real schema. A placeholder card carries a name and one hint line, so a model that has to guess a shape copies the local pattern of the sibling tools it can see — and a wrong guess used to cost four rounds (call → a rejection teaching a `ToolSearch` round trip → search → real call). A shape-invalid direct call now comes back as structured invalid-arguments **with the real declared parameter schema inlined**, and activates the tool on the spot — the same activation the `ToolSearch` `select:` lane performs (full schema materialized into the next request, fingerprint/boundary-announce refresh, listing ride), because both lanes run one activation closure. Wrong guess: four rounds → two. Right guess: still zero — the schema-valid direct lane is untouched, the shape gate still refuses to run the real tool body on a call it never declared, and adjudication is unchanged (policy/approval decide before execute, so a denied call activates nothing). Two consequences stated plainly: the rejection is flagged as an error, so a later leg's transcript replay does not credit that activation (the name reverts to a placeholder and the corrected call re-activates it — self-healing, never a widening); and a withdrawn name, or a declaration that cannot be serialized, keeps the previous "activate it first" rejection.
|
|
10
|
+
- Scheduler isolation keys fork by lifetime. A durable cron job was filed under the same key as a session-scoped one — minted `sessionId ?? principal ?? taskId ?? "default"` — so on any host with sessions a durable job belonged to the session that created it: it kept firing, but every later session got `not_found` from `CronList`/`CronDelete` and could not even learn it existed. Durable jobs are now isolated by the stable cross-session identity (`principal`, or a single `"default"` bucket where the deployment carries no principal); session-scoped jobs keep the session chain unchanged. `CronList` reads both lanes (deduplicated, and a lane whose listing fails surfaces the error instead of a half-empty list), `CronDelete` acts on the lane that actually holds the id, and `ScheduleWakeup` stays on the session lane (one session's `stop: true` never touches another's pending wakeup). The cross-tenant refusal is unchanged: another principal's durable job answers `not_found`, never an existence leak. **Deployment note**: durable rows written by earlier versions are still keyed by their originating session — a one-time re-key of existing rows belongs to the scheduler backend; the engine does no migration.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- The redaction findings channel completes (external-audit architecture item): findings now carry `source` (a closed provenance vocabulary — `arg-summary` / `untrusted-egress` / `secret-env` — stamped by the minting rule table, with no public finding constructor to forge); `summarizeRedactions(findings)` renders the one-line disclosure a host can announce ("3 redactions: prefixed-token (high) x1, …" — pure function, never auto-injected); and the env arm joins the channel (`scrubSecretEnv(env, findings?)` reports each deleted entry as a `SecretEnvFinding {key, kind, confidence, source}` — deletion, not replacement, so it is its own shape; one summarizer renders both). Absent the optional collector every path is byte-identical to before.
|
|
15
|
+
|
|
3
16
|
## 5.4.0 (2026-08-03)
|
|
4
17
|
|
|
5
18
|
_The external-audit dunning batch closes end-to-end (small cases + all three cleared A-group debts), and the engine grows its outer brain-call guardrail. All additive or louder; major stays locked at 5._
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
export type RedactionConfidence = "high" | "medium" | "low";
|
|
2
|
+
export type RedactionSource = "arg-summary" | "untrusted-egress" | "secret-env";
|
|
2
3
|
export interface RedactionFinding {
|
|
3
4
|
kind: string;
|
|
4
5
|
span: readonly [number, number];
|
|
5
6
|
confidence: RedactionConfidence;
|
|
6
7
|
marker: string;
|
|
8
|
+
source: RedactionSource;
|
|
7
9
|
}
|
|
10
|
+
export interface SummarizableFinding {
|
|
11
|
+
kind: string;
|
|
12
|
+
confidence: RedactionConfidence;
|
|
13
|
+
}
|
|
14
|
+
export declare function summarizeRedactions(findings: readonly SummarizableFinding[]): string;
|
|
8
15
|
export interface RedactionReport {
|
|
9
16
|
findings: RedactionFinding[];
|
|
10
17
|
preexistingMarkers?: number;
|
|
@@ -13,6 +20,7 @@ export interface RedactionPass {
|
|
|
13
20
|
kind: string;
|
|
14
21
|
confidence: RedactionConfidence;
|
|
15
22
|
marker: string;
|
|
23
|
+
source: RedactionSource;
|
|
16
24
|
re: RegExp;
|
|
17
25
|
replace: string | ((match: string, ...groups: string[]) => string);
|
|
18
26
|
}
|
package/dist/core/arg-summary.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
const ACTIVITY_ARG_MAX = 80;
|
|
2
2
|
const SENSITIVE_KEY = /token|secret|key|password|passwd|credential|auth/i;
|
|
3
|
+
export function summarizeRedactions(findings) {
|
|
4
|
+
if (findings.length === 0)
|
|
5
|
+
return "no redactions";
|
|
6
|
+
const counts = new Map();
|
|
7
|
+
for (const f of findings) {
|
|
8
|
+
const label = `${f.kind} (${f.confidence})`;
|
|
9
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
10
|
+
}
|
|
11
|
+
const parts = [...counts].map(([label, n]) => `${label} x${n}`);
|
|
12
|
+
return `${findings.length} redaction${findings.length === 1 ? "" : "s"}: ${parts.join(", ")}`;
|
|
13
|
+
}
|
|
3
14
|
function mapBackOnePass(edits, pos) {
|
|
4
15
|
let delta = 0;
|
|
5
16
|
for (const e of edits) {
|
|
@@ -37,7 +48,13 @@ export function runRedactionPasses(input, passes, report) {
|
|
|
37
48
|
s0 = mapBackOnePass(batches[i], s0);
|
|
38
49
|
e0 = mapBackOnePass(batches[i], e0);
|
|
39
50
|
}
|
|
40
|
-
report.findings.push({
|
|
51
|
+
report.findings.push({
|
|
52
|
+
kind: pass.kind,
|
|
53
|
+
confidence: pass.confidence,
|
|
54
|
+
span: [s0, e0],
|
|
55
|
+
marker: pass.marker,
|
|
56
|
+
source: pass.source,
|
|
57
|
+
});
|
|
41
58
|
}
|
|
42
59
|
edits.push({ at: offset, removedLen: match.length, insertedLen: inserted.length });
|
|
43
60
|
}
|
|
@@ -48,18 +65,19 @@ export function runRedactionPasses(input, passes, report) {
|
|
|
48
65
|
return cur;
|
|
49
66
|
}
|
|
50
67
|
export const SECRET_PASSES = [
|
|
51
|
-
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])(?:sk|pk|rk|gh[opsur])[-_][A-Za-z0-9_-]{8,}/g, replace: "[redacted]" },
|
|
52
|
-
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{20,}/g, replace: "[redacted]" },
|
|
53
|
-
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])xox[baprs]-[A-Za-z0-9-]{10,}/g, replace: "[redacted]" },
|
|
54
|
-
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{12,}/g, replace: "[redacted]" },
|
|
55
|
-
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{20,}/g, replace: "[redacted]" },
|
|
56
|
-
{ kind: "jwt", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])eyJ[A-Za-z0-9._-]{20,}/g, replace: "[redacted]" },
|
|
57
|
-
{ kind: "private-key-block", confidence: "high", marker: "[redacted]", re: /-----BEGIN[ A-Z]*PRIVATE KEY-----[\s\S]*?(?:-----END[ A-Z]*PRIVATE KEY-----|$)/g, replace: "[redacted]" },
|
|
58
|
-
{ kind: "bearer-token", confidence: "high", marker: "[redacted]", re: /(?<![A-Za-z0-9])Bearer\s+[A-Za-z0-9._~+/-]{8,}=*/gi, replace: "[redacted]" },
|
|
68
|
+
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])(?:sk|pk|rk|gh[opsur])[-_][A-Za-z0-9_-]{8,}/g, replace: "[redacted]" },
|
|
69
|
+
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{20,}/g, replace: "[redacted]" },
|
|
70
|
+
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])xox[baprs]-[A-Za-z0-9-]{10,}/g, replace: "[redacted]" },
|
|
71
|
+
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{12,}/g, replace: "[redacted]" },
|
|
72
|
+
{ kind: "prefixed-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{20,}/g, replace: "[redacted]" },
|
|
73
|
+
{ kind: "jwt", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])eyJ[A-Za-z0-9._-]{20,}/g, replace: "[redacted]" },
|
|
74
|
+
{ kind: "private-key-block", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /-----BEGIN[ A-Z]*PRIVATE KEY-----[\s\S]*?(?:-----END[ A-Z]*PRIVATE KEY-----|$)/g, replace: "[redacted]" },
|
|
75
|
+
{ kind: "bearer-token", confidence: "high", marker: "[redacted]", source: "arg-summary", re: /(?<![A-Za-z0-9])Bearer\s+[A-Za-z0-9._~+/-]{8,}=*/gi, replace: "[redacted]" },
|
|
59
76
|
{
|
|
60
77
|
kind: "keyword-value",
|
|
61
78
|
confidence: "medium",
|
|
62
79
|
marker: "[redacted]",
|
|
80
|
+
source: "arg-summary",
|
|
63
81
|
re: /(?<![A-Za-z0-9])((?:token|secret|key|password|passwd|credential|authorization)["']?\s*[=:]\s*["']?)[^\s"';|&]{4,}/gi,
|
|
64
82
|
replace: "$1[redacted]",
|
|
65
83
|
},
|
|
@@ -1469,9 +1469,9 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1469
1469
|
if (spec.interactiveTools === true || (spec.interactiveTools !== false && (onQuestion !== undefined || durableQuestionFace)))
|
|
1470
1470
|
tools.push(createAskUserQuestionTool(onQuestion, { principal: spec.principal, sourceTaskId: sessionId }));
|
|
1471
1471
|
if (spec.handsReadOnly !== true) {
|
|
1472
|
-
const
|
|
1472
|
+
const sessionScope = sessionId ?? spec.principal ?? spec.taskId ?? "default";
|
|
1473
1473
|
tools.push(...createSchedulerTools(executionEnv, {
|
|
1474
|
-
scope:
|
|
1474
|
+
scope: sessionScope,
|
|
1475
1475
|
...(spec.principal !== undefined ? { principal: spec.principal } : {}),
|
|
1476
1476
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
1477
1477
|
requestStopAfterTurn,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { Value } from "typebox/value";
|
|
3
3
|
import { defineTool, errorResult } from "../tools.js";
|
|
4
|
+
import { formatZodValidationError, truncateError } from "../tool-errors.js";
|
|
4
5
|
export const TOOL_SEARCH_NAME = "ToolSearch";
|
|
5
6
|
const DEFER_AUTO_FRACTION = 0.1;
|
|
6
7
|
const CHARS_PER_TOKEN = 4;
|
|
@@ -61,12 +62,32 @@ export function buildDeferredRegistry(deferred, tools) {
|
|
|
61
62
|
}
|
|
62
63
|
return reg;
|
|
63
64
|
}
|
|
65
|
+
function renderSchemaForModel(schema) {
|
|
66
|
+
let json;
|
|
67
|
+
try {
|
|
68
|
+
json = JSON.stringify(schema);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
return json === undefined ? undefined : truncateError(json);
|
|
74
|
+
}
|
|
64
75
|
export function createPlaceholderTool(info, direct) {
|
|
65
76
|
const sn = safeName(info.name);
|
|
66
77
|
const teachingRejection = () => {
|
|
67
78
|
throw new Error(`Tool "${sn}" is not active yet. Call ${TOOL_SEARCH_NAME} with {"query":"select:${sn}"} ` +
|
|
68
79
|
`(or a keyword \`query\`) to load its full schema, then call ${sn} with the proper arguments.`);
|
|
69
80
|
};
|
|
81
|
+
const invalidArgumentsRejection = (target, params, schemaJson, ride) => {
|
|
82
|
+
const text = `Invalid arguments for \`${sn}\`: ${formatZodValidationError(target.parameters, params)} ` +
|
|
83
|
+
`\`${sn}\` is now active — its full parameter schema is below (and rides the next request). ` +
|
|
84
|
+
`Call \`${sn}\` again with arguments matching it.\nParameter schema: ${schemaJson}`;
|
|
85
|
+
return {
|
|
86
|
+
content: ride === undefined || ride === "" ? [{ type: "text", text }] : [{ type: "text", text }, { type: "text", text: ride }],
|
|
87
|
+
details: { invalidArguments: true },
|
|
88
|
+
isError: true,
|
|
89
|
+
};
|
|
90
|
+
};
|
|
70
91
|
if (direct !== undefined) {
|
|
71
92
|
return {
|
|
72
93
|
name: info.name,
|
|
@@ -78,14 +99,20 @@ export function createPlaceholderTool(info, direct) {
|
|
|
78
99
|
...(direct.executionMode !== undefined ? { executionMode: direct.executionMode } : {}),
|
|
79
100
|
execute: async (toolCallId, params, signal, onUpdate) => {
|
|
80
101
|
const real = direct.resolveReal();
|
|
81
|
-
if (real
|
|
102
|
+
if (real === undefined)
|
|
103
|
+
return teachingRejection();
|
|
104
|
+
if (Value.Check(real.parameters, params)) {
|
|
82
105
|
const ride = await direct.activate();
|
|
83
106
|
const result = await real.invoke(toolCallId, params, signal, onUpdate);
|
|
84
107
|
if (ride === undefined || ride === "")
|
|
85
108
|
return result;
|
|
86
109
|
return { ...result, content: [...result.content, { type: "text", text: ride }] };
|
|
87
110
|
}
|
|
88
|
-
|
|
111
|
+
const schemaJson = renderSchemaForModel(real.parameters);
|
|
112
|
+
if (schemaJson === undefined)
|
|
113
|
+
return teachingRejection();
|
|
114
|
+
const ride = await direct.activate();
|
|
115
|
+
return invalidArgumentsRejection(real, params, schemaJson, ride);
|
|
89
116
|
},
|
|
90
117
|
};
|
|
91
118
|
}
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
+
import type { RedactionConfidence, RedactionSource } from "./arg-summary.js";
|
|
2
|
+
export type SecretEnvFindingKind = "suffix-rule" | "exact-name";
|
|
3
|
+
export interface SecretEnvFinding {
|
|
4
|
+
key: string;
|
|
5
|
+
kind: SecretEnvFindingKind;
|
|
6
|
+
confidence: RedactionConfidence;
|
|
7
|
+
source: RedactionSource;
|
|
8
|
+
}
|
|
1
9
|
export declare function isSecretEnvKey(key: string): boolean;
|
|
2
|
-
export declare function scrubSecretEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
10
|
+
export declare function scrubSecretEnv(env: NodeJS.ProcessEnv, findings?: SecretEnvFinding[]): NodeJS.ProcessEnv;
|
package/dist/core/secret-env.js
CHANGED
|
@@ -4,14 +4,31 @@ const SECRET_ENV_EXACT_NAMES = new Set([
|
|
|
4
4
|
"PGPASSWORD", "MYSQL_PWD", "SSHPASS", "GPG_PASSPHRASE", "GH_PAT", "GITHUB_PAT",
|
|
5
5
|
"NPM_CONFIG__AUTH", "NPM_CONFIG__AUTHTOKEN",
|
|
6
6
|
]);
|
|
7
|
+
const SECRET_ENV_KIND_CONFIDENCE = {
|
|
8
|
+
"exact-name": "high",
|
|
9
|
+
"suffix-rule": "medium",
|
|
10
|
+
};
|
|
11
|
+
function classifySecretEnvKey(key) {
|
|
12
|
+
if (SECRET_ENV_RE.test(key))
|
|
13
|
+
return "suffix-rule";
|
|
14
|
+
if (SECRET_ENV_EXACT_NAMES.has(key.toUpperCase()))
|
|
15
|
+
return "exact-name";
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
7
18
|
export function isSecretEnvKey(key) {
|
|
8
|
-
return
|
|
19
|
+
return classifySecretEnvKey(key) !== undefined;
|
|
9
20
|
}
|
|
10
|
-
export function scrubSecretEnv(env) {
|
|
21
|
+
export function scrubSecretEnv(env, findings) {
|
|
11
22
|
const out = {};
|
|
12
23
|
for (const k of Object.keys(env)) {
|
|
13
|
-
|
|
24
|
+
const kind = classifySecretEnvKey(k);
|
|
25
|
+
if (kind === undefined) {
|
|
14
26
|
out[k] = env[k];
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (findings !== undefined) {
|
|
30
|
+
findings.push({ key: k, kind, confidence: SECRET_ENV_KIND_CONFIDENCE[kind], source: "secret-env" });
|
|
31
|
+
}
|
|
15
32
|
}
|
|
16
33
|
return out;
|
|
17
34
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type RedactionReport } from "./arg-summary.js";
|
|
2
|
-
export type { RedactionFinding, RedactionReport, RedactionConfidence } from "./arg-summary.js";
|
|
2
|
+
export type { RedactionFinding, RedactionReport, RedactionConfidence, RedactionSource, SummarizableFinding, } from "./arg-summary.js";
|
|
3
|
+
export { summarizeRedactions } from "./arg-summary.js";
|
|
3
4
|
export declare function redactSecrets(s: string, report?: RedactionReport): string;
|
|
4
5
|
export declare function redactHostLeaks(s: string, report?: RedactionReport): string;
|
|
5
6
|
export declare function boundedRedactedSummary(value: unknown, max: number): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { delimitUntrusted } from "./untrusted-text.js";
|
|
2
2
|
import { scrubSecrets, SECRET_PASSES, runRedactionPasses } from "./arg-summary.js";
|
|
3
3
|
import { sliceHeadSafe } from "./surrogate-safe-slice.js";
|
|
4
|
+
export { summarizeRedactions } from "./arg-summary.js";
|
|
4
5
|
const URI_SCHEME = String.raw `[A-Za-z][A-Za-z0-9+.\-_]{0,63}`;
|
|
5
6
|
const CREDENTIALS_IN_AUTHORITY = new RegExp(String.raw `(${URI_SCHEME}:\/\/)((?:[^/\s"'@]*@)+)`, "gi");
|
|
6
7
|
const URL_OF_ANY_PROTOCOL = new RegExp(String.raw `${URI_SCHEME}:\/\/[^\s"'\`]+`, "gi");
|
|
@@ -21,18 +22,19 @@ const SECRET_TIER_PASSES = [
|
|
|
21
22
|
kind: "url-credentials",
|
|
22
23
|
confidence: "medium",
|
|
23
24
|
marker: "[redacted-credentials]",
|
|
25
|
+
source: "untrusted-egress",
|
|
24
26
|
re: CREDENTIALS_IN_AUTHORITY,
|
|
25
27
|
replace: (_m, scheme, userinfo) => redactUserinfo(scheme, userinfo),
|
|
26
28
|
},
|
|
27
|
-
{ kind: "signed-url-param", confidence: "high", marker: "[redacted]", re: /([?&#](?:sig|signature|sas|code)=)[^&\s"'#]+/gi, replace: "$1[redacted]" },
|
|
29
|
+
{ kind: "signed-url-param", confidence: "high", marker: "[redacted]", source: "untrusted-egress", re: /([?&#](?:sig|signature|sas|code)=)[^&\s"'#]+/gi, replace: "$1[redacted]" },
|
|
28
30
|
];
|
|
29
31
|
export function redactSecrets(s, report) {
|
|
30
32
|
return runRedactionPasses(s, SECRET_TIER_PASSES, report);
|
|
31
33
|
}
|
|
32
34
|
const HOST_LEAK_PASSES = [
|
|
33
35
|
...SECRET_TIER_PASSES,
|
|
34
|
-
{ kind: "url", confidence: "medium", marker: "[redacted-url]", re: URL_OF_ANY_PROTOCOL, replace: "[redacted-url]" },
|
|
35
|
-
{ kind: "fs-path", confidence: "low", marker: "[redacted-path]", re: /(?:\/[A-Za-z0-9_.-]+){2,}/g, replace: "[redacted-path]" },
|
|
36
|
+
{ kind: "url", confidence: "medium", marker: "[redacted-url]", source: "untrusted-egress", re: URL_OF_ANY_PROTOCOL, replace: "[redacted-url]" },
|
|
37
|
+
{ kind: "fs-path", confidence: "low", marker: "[redacted-path]", source: "untrusted-egress", re: /(?:\/[A-Za-z0-9_.-]+){2,}/g, replace: "[redacted-path]" },
|
|
36
38
|
];
|
|
37
39
|
export function redactHostLeaks(s, report) {
|
|
38
40
|
return runRedactionPasses(s, HOST_LEAK_PASSES, report);
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export { killProcessTree, signalProcessTree } from "./engine/execution-env/kill-
|
|
|
57
57
|
export type { KillProcessTreeOptions } from "./engine/execution-env/kill-tree.js";
|
|
58
58
|
export { getShellConfig, isWslBashLauncher } from "./engine/execution-env/node-execution-env.js";
|
|
59
59
|
export { isSecretEnvKey, scrubSecretEnv } from "./core/secret-env.js";
|
|
60
|
+
export type { SecretEnvFinding, SecretEnvFindingKind } from "./core/secret-env.js";
|
|
60
61
|
export { MAX_EXEC_OUTPUT_BYTES, RollingTailBuffer, markTruncated } from "./core/exec-output-tail.js";
|
|
61
62
|
export type { ExecutionEnv, FileInfo, Result, FileErrorCode, ExecutionErrorCode } from "./internal/harness.js";
|
|
62
63
|
export type { ExecResult } from "./internal/harness.js";
|
|
@@ -172,7 +173,8 @@ export { FileBackgroundAgentStore, type FileBackgroundAgentStoreOptions } from "
|
|
|
172
173
|
export { A2A_TASK_STATES, type A2ATaskState, type A2ATaskStateReversal, type A2ATaskStateReversalFaithful, type A2ATaskStateReversalLossy, toA2ATaskState, fromA2ATaskState, } from "./core/a2a-task-state.js";
|
|
173
174
|
export { type WorkflowJournalStore, type WorkflowJournalEntry, InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
|
|
174
175
|
export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
|
|
175
|
-
export type { RedactionFinding, RedactionReport, RedactionConfidence } from "./core/untrusted-egress.js";
|
|
176
|
+
export type { RedactionFinding, RedactionReport, RedactionConfidence, RedactionSource, SummarizableFinding, } from "./core/untrusted-egress.js";
|
|
177
|
+
export { summarizeRedactions } from "./core/untrusted-egress.js";
|
|
176
178
|
export { MemoryRosterStore, FileRosterStore, type RosterStore, type RosterEntry, type RosterAccess, type RosterGcOptions } from "./agents/roster-store.js";
|
|
177
179
|
export { CONFIG_CATALOG_VERSION, describeConfigCatalog, resolveEffectiveConfig, type ConfigKnob, type ConfigOverrideDeclaration, type ConfigProvenance, type EffectiveConfigField, } from "./config/catalog.js";
|
|
178
180
|
export { normalizeAgentName } from "./core/task-registry.js";
|
package/dist/index.js
CHANGED
|
@@ -157,6 +157,7 @@ export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.j
|
|
|
157
157
|
export { A2A_TASK_STATES, toA2ATaskState, fromA2ATaskState, } from "./core/a2a-task-state.js";
|
|
158
158
|
export { InMemoryWorkflowJournalStore, callKeyOrdinal, JOURNAL_OVERSIZE_ERROR_CODE, } from "./core/workflow-journal-store.js";
|
|
159
159
|
export { untrustedEgressForHuman, redactHostLeaks, redactSecrets, boundedRedactedSummary } from "./core/untrusted-egress.js";
|
|
160
|
+
export { summarizeRedactions } from "./core/untrusted-egress.js";
|
|
160
161
|
export { MemoryRosterStore, FileRosterStore } from "./agents/roster-store.js";
|
|
161
162
|
export { CONFIG_CATALOG_VERSION, describeConfigCatalog, resolveEffectiveConfig, } from "./config/catalog.js";
|
|
162
163
|
export { normalizeAgentName } from "./core/task-registry.js";
|
|
@@ -27,9 +27,12 @@ The runtime clamps to [60, 3600], so you don't need to clamp yourself.
|
|
|
27
27
|
|
|
28
28
|
One short sentence on what you chose and why. Goes to telemetry and is shown back to the user. "watching CI run" beats "waiting." The user reads this to understand what you're doing without having to predict your cadence in advance — make it specific.
|
|
29
29
|
`;
|
|
30
|
-
function
|
|
30
|
+
function durableScopeOf(c) {
|
|
31
|
+
return c.principal ?? "default";
|
|
32
|
+
}
|
|
33
|
+
function toSchedulerContext(c, scope) {
|
|
31
34
|
return {
|
|
32
|
-
scope
|
|
35
|
+
scope,
|
|
33
36
|
...(c.principal !== undefined ? { principal: c.principal } : {}),
|
|
34
37
|
...(c.sessionId !== undefined ? { sessionId: c.sessionId } : {}),
|
|
35
38
|
...(c.taskConfig !== undefined ? { taskConfig: c.taskConfig } : {}),
|
|
@@ -157,7 +160,9 @@ export function createSchedulerTools(env, ctx) {
|
|
|
157
160
|
if (!hasScheduler(env))
|
|
158
161
|
return [];
|
|
159
162
|
const sched = env;
|
|
160
|
-
const
|
|
163
|
+
const sessionCtx = toSchedulerContext(ctx, ctx.scope);
|
|
164
|
+
const durableCtx = toSchedulerContext(ctx, durableScopeOf(ctx));
|
|
165
|
+
const lanesDiffer = sessionCtx.scope !== durableCtx.scope;
|
|
161
166
|
let cronCreateChain = Promise.resolve();
|
|
162
167
|
const serializedCronCreateOp = (fn) => {
|
|
163
168
|
const next = cronCreateChain.then(fn, fn);
|
|
@@ -255,9 +260,10 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
255
260
|
...(!durable ? { lifetime: "session" } : {}),
|
|
256
261
|
...(a.recurring !== undefined ? { recurring: a.recurring } : {}),
|
|
257
262
|
};
|
|
258
|
-
const
|
|
263
|
+
const opCtx = durable ? durableCtx : sessionCtx;
|
|
264
|
+
const before = await sched.list(opCtx);
|
|
259
265
|
const knownIds = before.ok ? new Set(before.value.map((s) => s.id)) : undefined;
|
|
260
|
-
const r = await sched.schedule(intent,
|
|
266
|
+
const r = await sched.schedule(intent, opCtx);
|
|
261
267
|
if (!r.ok)
|
|
262
268
|
return errorResult(`Error (CronCreate): ${r.error.message}`);
|
|
263
269
|
const replaced = knownIds?.has(r.value.id);
|
|
@@ -285,6 +291,16 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
285
291
|
};
|
|
286
292
|
}),
|
|
287
293
|
});
|
|
294
|
+
const cancelLanesFor = async (id) => {
|
|
295
|
+
if (!lanesDiffer)
|
|
296
|
+
return [sessionCtx];
|
|
297
|
+
for (const lane of [sessionCtx, durableCtx]) {
|
|
298
|
+
const listed = await sched.list(lane);
|
|
299
|
+
if (listed.ok && listed.value.some((s) => s.id === id))
|
|
300
|
+
return [lane];
|
|
301
|
+
}
|
|
302
|
+
return [sessionCtx, durableCtx];
|
|
303
|
+
};
|
|
288
304
|
const cronCancel = defineTool({
|
|
289
305
|
name: "CronDelete",
|
|
290
306
|
contract: { contractId: "core.cron_delete@1", implementationRevision: "1" },
|
|
@@ -293,7 +309,14 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
293
309
|
effect: "idempotent",
|
|
294
310
|
execute: async (args) => {
|
|
295
311
|
const { id } = args;
|
|
296
|
-
const
|
|
312
|
+
const taskId = id;
|
|
313
|
+
const [firstLane, ...otherLanes] = await cancelLanesFor(taskId);
|
|
314
|
+
let r = await sched.cancel(taskId, firstLane);
|
|
315
|
+
for (const lane of otherLanes) {
|
|
316
|
+
if (r.ok || r.error.code !== "not_found")
|
|
317
|
+
break;
|
|
318
|
+
r = await sched.cancel(taskId, lane);
|
|
319
|
+
}
|
|
297
320
|
if (!r.ok) {
|
|
298
321
|
if (r.error.code === "not_found")
|
|
299
322
|
return `CronDelete: no scheduled task "${id}" (already gone or not yours).`;
|
|
@@ -309,13 +332,23 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
309
332
|
parameters: Type.Object({}),
|
|
310
333
|
effect: "read",
|
|
311
334
|
execute: async () => {
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
335
|
+
const rows = [];
|
|
336
|
+
const seen = new Set();
|
|
337
|
+
for (const lane of lanesDiffer ? [sessionCtx, durableCtx] : [sessionCtx]) {
|
|
338
|
+
const listed = await sched.list(lane);
|
|
339
|
+
if (!listed.ok)
|
|
340
|
+
return errorResult(`Error (CronList): ${listed.error.message}`);
|
|
341
|
+
for (const s of listed.value) {
|
|
342
|
+
if (seen.has(s.id))
|
|
343
|
+
continue;
|
|
344
|
+
seen.add(s.id);
|
|
345
|
+
rows.push(s);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (rows.length === 0) {
|
|
316
349
|
return { content: "No scheduled tasks.", details: { type: "cron-list", jobs: [] } };
|
|
317
350
|
}
|
|
318
|
-
const content =
|
|
351
|
+
const content = rows
|
|
319
352
|
.map((s) => {
|
|
320
353
|
const shown = cronExprFromSummary(s.when);
|
|
321
354
|
const human = cronToHuman(s.when);
|
|
@@ -324,7 +357,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
324
357
|
return `- ${s.id}: ${shown}${human !== s.when ? ` — ${human}` : ""}${s.label ? ` (${s.label})` : ""}${tier}${once}`;
|
|
325
358
|
})
|
|
326
359
|
.join("\n");
|
|
327
|
-
const jobs =
|
|
360
|
+
const jobs = rows.map((s) => {
|
|
328
361
|
const human = cronToHuman(s.when);
|
|
329
362
|
return {
|
|
330
363
|
id: s.id,
|
|
@@ -339,7 +372,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
339
372
|
},
|
|
340
373
|
});
|
|
341
374
|
const listPendingWakeups = async () => {
|
|
342
|
-
const r = await sched.list(
|
|
375
|
+
const r = await sched.list(sessionCtx);
|
|
343
376
|
if (!r.ok)
|
|
344
377
|
return { err: r.error.message };
|
|
345
378
|
return {
|
|
@@ -351,7 +384,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
351
384
|
const cancelWakeups = async (ids) => {
|
|
352
385
|
let n = 0;
|
|
353
386
|
for (const id of ids) {
|
|
354
|
-
const r = await sched.cancel(id,
|
|
387
|
+
const r = await sched.cancel(id, sessionCtx);
|
|
355
388
|
if (r.ok)
|
|
356
389
|
n += 1;
|
|
357
390
|
}
|
|
@@ -405,7 +438,7 @@ Only use minute 0 or 30 when the user names that exact time and clearly means it
|
|
|
405
438
|
const pending = await listPendingWakeups();
|
|
406
439
|
if (!("err" in pending))
|
|
407
440
|
await cancelWakeups(pending.ids);
|
|
408
|
-
const r = await sched.schedule({ prompt: a.prompt, when: { kind: "delay", delaySec: clampedDelaySeconds }, label: WAKEUP_LABEL, mode: "session-wakeup" },
|
|
441
|
+
const r = await sched.schedule({ prompt: a.prompt, when: { kind: "delay", delaySec: clampedDelaySeconds }, label: WAKEUP_LABEL, mode: "session-wakeup" }, sessionCtx);
|
|
409
442
|
if (!r.ok)
|
|
410
443
|
return errorResult(`Error (${SCHEDULE_WAKEUP_TOOL_NAME}): ${r.error.message}`);
|
|
411
444
|
const scheduledFor = Date.now() + clampedDelaySeconds * 1000;
|