@sema-agent/core 1.424.0 → 1.425.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/dist/core/auto-compaction.d.ts +4 -0
- package/dist/core/auto-compaction.js +8 -0
- package/dist/core/mcp.d.ts +15 -0
- package/dist/core/mcp.js +296 -49
- package/dist/core/runner/prepare-task.d.ts +4 -0
- package/dist/core/runner/prepare-task.js +6 -2
- package/dist/core/runner/runtask.js +3 -0
- package/dist/tools/fs/index.d.ts +4 -0
- package/dist/tools/fs/index.js +11 -0
- package/package.json +1 -1
|
@@ -67,6 +67,10 @@ export interface MaybeCompactOptions {
|
|
|
67
67
|
maxCharsPerFile?: number;
|
|
68
68
|
recentlyReadFiles?: () => string[];
|
|
69
69
|
};
|
|
70
|
+
onApplied?: (attachedComplete: ReadonlyArray<{
|
|
71
|
+
path: string;
|
|
72
|
+
content: string;
|
|
73
|
+
}>) => void;
|
|
70
74
|
overheadTokens?: number;
|
|
71
75
|
summaryProvider?: (input: {
|
|
72
76
|
messagesToSummarize: AgentMessage[];
|
|
@@ -315,6 +315,7 @@ export async function maybeCompact(opts) {
|
|
|
315
315
|
const persistStartAt = summaryEndAt ?? Date.now();
|
|
316
316
|
let summaryWithAttachments = summary;
|
|
317
317
|
const attachedFiles = [];
|
|
318
|
+
const attachedComplete = [];
|
|
318
319
|
const att = opts.workingFileAttachments;
|
|
319
320
|
const recentlyRead = att?.recentlyReadFiles?.() ?? [];
|
|
320
321
|
const candidateFiles = recentlyRead.length > 0 ? recentlyRead : (details?.modifiedFilesByRecency ?? details?.modifiedFiles);
|
|
@@ -343,6 +344,8 @@ export async function maybeCompact(opts) {
|
|
|
343
344
|
remaining -= clipped.length;
|
|
344
345
|
blocks.push(`<working-file path="${path}">\n${clipped}\n</working-file>`);
|
|
345
346
|
attachedFiles.push({ path, chars: Math.min(content.length, cap), truncated: wasClipped });
|
|
347
|
+
if (!wasClipped)
|
|
348
|
+
attachedComplete.push({ path, content });
|
|
346
349
|
}
|
|
347
350
|
if (blocks.length > 0) {
|
|
348
351
|
summaryWithAttachments +=
|
|
@@ -365,6 +368,11 @@ export async function maybeCompact(opts) {
|
|
|
365
368
|
promptEpoch: restatedEpoch,
|
|
366
369
|
...(restatedListings !== undefined ? { announcedListings: restatedListings } : {}),
|
|
367
370
|
}, false);
|
|
371
|
+
try {
|
|
372
|
+
opts.onApplied?.(attachedComplete);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
}
|
|
368
376
|
if (ca !== undefined) {
|
|
369
377
|
try {
|
|
370
378
|
ca.apply(restatedEpoch.artifactDigest);
|
package/dist/core/mcp.d.ts
CHANGED
|
@@ -64,6 +64,9 @@ export declare function truncateMcpErrorText(s: string): string;
|
|
|
64
64
|
export declare const MCP_TOOL_TIMEOUT_DEFAULT_MS = 100000000;
|
|
65
65
|
export declare function mcpToolTimeoutMs(): number;
|
|
66
66
|
export declare function mcpToolTotalTimeoutMs(perCallMs: number): number;
|
|
67
|
+
export declare const MCP_IDLE_TIMEOUT_STDIO_DEFAULT_MS: number;
|
|
68
|
+
export declare const MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS: number;
|
|
69
|
+
export declare function mcpIdleTimeoutMs(kind: "stdio" | "http"): number;
|
|
67
70
|
export declare function normalizeMcpName(name: string): string;
|
|
68
71
|
export declare function clampNameSegment(seg: string, max?: number): string;
|
|
69
72
|
export declare const MCP_IMAGE_MAX_BASE64: number;
|
|
@@ -130,6 +133,18 @@ interface McpContentItem {
|
|
|
130
133
|
};
|
|
131
134
|
}
|
|
132
135
|
export declare function mapContent(content: Array<McpContentItem>, serverName?: string, imageResizer?: McpImageResizer): Promise<Array<TextContent | ImageContent>>;
|
|
136
|
+
export type McpSchemaNormalizeResult = {
|
|
137
|
+
outcome: "unchanged";
|
|
138
|
+
} | {
|
|
139
|
+
outcome: "normalized";
|
|
140
|
+
schema: Record<string, unknown>;
|
|
141
|
+
note: string;
|
|
142
|
+
combinators: string[];
|
|
143
|
+
} | {
|
|
144
|
+
outcome: "drop";
|
|
145
|
+
reason: string;
|
|
146
|
+
};
|
|
147
|
+
export declare function normalizeMcpToolSchema(schema: unknown): McpSchemaNormalizeResult;
|
|
133
148
|
export declare function mcpToolSchemaProblem(schema: unknown): string | undefined;
|
|
134
149
|
export declare function materializeMcpTools(specs: McpServerSpec[], principal?: string, onElicit?: OnElicit, imageResizer?: McpImageResizer): Promise<MaterializedMcp>;
|
|
135
150
|
export {};
|
package/dist/core/mcp.js
CHANGED
|
@@ -96,6 +96,43 @@ export function mcpToolTimeoutMs() {
|
|
|
96
96
|
export function mcpToolTotalTimeoutMs(perCallMs) {
|
|
97
97
|
return parseEnvMs("MCP_TOOL_TIMEOUT_TOTAL") ?? perCallMs;
|
|
98
98
|
}
|
|
99
|
+
export const MCP_IDLE_TIMEOUT_STDIO_DEFAULT_MS = 30 * 60_000;
|
|
100
|
+
export const MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS = 5 * 60_000;
|
|
101
|
+
export function mcpIdleTimeoutMs(kind) {
|
|
102
|
+
return kind === "stdio"
|
|
103
|
+
? (parseEnvMs("MCP_IDLE_TIMEOUT_STDIO") ?? MCP_IDLE_TIMEOUT_STDIO_DEFAULT_MS)
|
|
104
|
+
: (parseEnvMs("MCP_IDLE_TIMEOUT_HTTP") ?? MCP_IDLE_TIMEOUT_HTTP_DEFAULT_MS);
|
|
105
|
+
}
|
|
106
|
+
function idleKindOf(spec) {
|
|
107
|
+
return spec.transport.kind === "stdio" ? "stdio" : "http";
|
|
108
|
+
}
|
|
109
|
+
const IDLE_WATCHDOG_ABORT_REASON = Symbol("mcp-idle-watchdog-abort");
|
|
110
|
+
function armMcpIdleWatchdog(health, idleMs, outerSignal) {
|
|
111
|
+
const idleController = new AbortController();
|
|
112
|
+
let timer;
|
|
113
|
+
const rearm = () => {
|
|
114
|
+
if (timer !== undefined)
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
const armedAt = Date.now();
|
|
117
|
+
timer = setTimeout(() => {
|
|
118
|
+
if (health.pendingElicitations > 0 || health.lastElicitationClosedAt > armedAt) {
|
|
119
|
+
rearm();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
idleController.abort(IDLE_WATCHDOG_ABORT_REASON);
|
|
123
|
+
}, idleMs);
|
|
124
|
+
};
|
|
125
|
+
rearm();
|
|
126
|
+
return {
|
|
127
|
+
combinedSignal: outerSignal !== undefined ? AbortSignal.any([outerSignal, idleController.signal]) : idleController.signal,
|
|
128
|
+
idleSignal: idleController.signal,
|
|
129
|
+
rearm,
|
|
130
|
+
dispose: () => {
|
|
131
|
+
if (timer !== undefined)
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
99
136
|
function mcpStartupTimeoutMs() {
|
|
100
137
|
return parseEnvMs("MCP_TIMEOUT");
|
|
101
138
|
}
|
|
@@ -110,6 +147,16 @@ function writeEffectWarning(writeEffect) {
|
|
|
110
147
|
: "";
|
|
111
148
|
}
|
|
112
149
|
function rethrowHonestMcpError(err, ctx) {
|
|
150
|
+
if (ctx.idle?.signal.reason === IDLE_WATCHDOG_ABORT_REASON) {
|
|
151
|
+
const serverLabel = inlineUntrusted(ctx.server);
|
|
152
|
+
const e = new Error(`${ctx.what} on MCP server "${serverLabel}" received no response for ${ctx.idle.idleMs}ms (idle watchdog — ` +
|
|
153
|
+
`distinct from the ${ctx.timeoutMs}ms total ceiling above). The server may still be alive but is not ` +
|
|
154
|
+
`responding; the client stopped waiting.${writeEffectWarning(ctx.writeEffect)} (Set MCP_IDLE_TIMEOUT_STDIO ` +
|
|
155
|
+
`/ MCP_IDLE_TIMEOUT_HTTP (ms) to change this bound.)`, { cause: err });
|
|
156
|
+
e.errorKind = "timeout";
|
|
157
|
+
e.details = { timedOut: true, timeoutMs: ctx.idle.idleMs, idleTimeout: true, server: ctx.server };
|
|
158
|
+
throw e;
|
|
159
|
+
}
|
|
113
160
|
if (ctx.signal?.aborted)
|
|
114
161
|
throw err;
|
|
115
162
|
const serverLabel = inlineUntrusted(ctx.server);
|
|
@@ -117,7 +164,7 @@ function rethrowHonestMcpError(err, ctx) {
|
|
|
117
164
|
const data = err.data;
|
|
118
165
|
const totalMs = typeof data?.maxTotalTimeout === "number" ? data.maxTotalTimeout : undefined;
|
|
119
166
|
const e = new Error(totalMs !== undefined
|
|
120
|
-
? `${ctx.what} on MCP server "${serverLabel}" exceeded its total time ceiling of ${totalMs}ms (the call stayed alive — e.g. via progress notifications — but ran past the total wall-clock budget). The client stopped waiting, but the server may still be executing the request — the outcome is unknown.${writeEffectWarning(ctx.writeEffect)} (Set the MCP_TOOL_TIMEOUT_TOTAL environment variable (ms) to change this ceiling; MCP_TOOL_TIMEOUT only tunes the
|
|
167
|
+
? `${ctx.what} on MCP server "${serverLabel}" exceeded its total time ceiling of ${totalMs}ms (the call stayed alive — e.g. via progress notifications — but ran past the total wall-clock budget). The client stopped waiting, but the server may still be executing the request — the outcome is unknown.${writeEffectWarning(ctx.writeEffect)} (Set the MCP_TOOL_TIMEOUT_TOTAL environment variable (ms) to change this ceiling; MCP_TOOL_TIMEOUT only tunes the per-call timer, currently ${ctx.timeoutMs}ms.)`
|
|
121
168
|
: `${ctx.what} on MCP server "${serverLabel}" timed out after ${ctx.timeoutMs}ms. The client stopped waiting, but the server may still be executing the request — the outcome is unknown.${writeEffectWarning(ctx.writeEffect)} (Set the MCP_TOOL_TIMEOUT environment variable (ms) to change this limit.)`, { cause: err });
|
|
122
169
|
e.errorKind = "timeout";
|
|
123
170
|
e.details =
|
|
@@ -419,6 +466,112 @@ export async function mapContent(content, serverName, imageResizer) {
|
|
|
419
466
|
}
|
|
420
467
|
return out;
|
|
421
468
|
}
|
|
469
|
+
const MCP_SCHEMA_COMBINATOR_KEYS = ["anyOf", "oneOf", "allOf"];
|
|
470
|
+
const MCP_SCHEMA_PROP_NAME_RE = /^[a-zA-Z0-9_.-]{1,64}$/;
|
|
471
|
+
const MCP_SCHEMA_CARRY_KEYS = ["$defs", "definitions", "$schema", "additionalProperties", "description", "title"];
|
|
472
|
+
function isPlainSchemaObject(v) {
|
|
473
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
474
|
+
}
|
|
475
|
+
function resolveLocalSchemaRef(fragment, root) {
|
|
476
|
+
const ref = fragment.$ref;
|
|
477
|
+
if (typeof ref !== "string")
|
|
478
|
+
return fragment;
|
|
479
|
+
const m = /^#\/(\$defs|definitions)\/([^/]+)$/.exec(ref);
|
|
480
|
+
if (m === null)
|
|
481
|
+
return fragment;
|
|
482
|
+
const bucket = root[m[1]];
|
|
483
|
+
if (!isPlainSchemaObject(bucket))
|
|
484
|
+
return fragment;
|
|
485
|
+
const resolved = bucket[m[2]];
|
|
486
|
+
return isPlainSchemaObject(resolved) ? resolved : fragment;
|
|
487
|
+
}
|
|
488
|
+
function summarizeSchemaBranch(branch) {
|
|
489
|
+
if (!isPlainSchemaObject(branch))
|
|
490
|
+
return null;
|
|
491
|
+
const required = branch.required;
|
|
492
|
+
if (Array.isArray(required) && required.length > 0 && required.every((n) => typeof n === "string")) {
|
|
493
|
+
return required.join(", ");
|
|
494
|
+
}
|
|
495
|
+
const properties = branch.properties;
|
|
496
|
+
if (isPlainSchemaObject(properties)) {
|
|
497
|
+
const names = Object.keys(properties);
|
|
498
|
+
if (names.length > 0)
|
|
499
|
+
return names.join(", ");
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
function buildSchemaFlattenNote(combinators, schema, hasAnyOfOrOneOf) {
|
|
504
|
+
if (!hasAnyOfOrOneOf) {
|
|
505
|
+
return "Input constraint: all listed parameters apply together (flattened from a JSON Schema allOf).";
|
|
506
|
+
}
|
|
507
|
+
const key = combinators.includes("oneOf") ? "oneOf" : "anyOf";
|
|
508
|
+
const branches = schema[key];
|
|
509
|
+
const summaries = Array.isArray(branches)
|
|
510
|
+
? [...new Set(branches.map((b) => summarizeSchemaBranch(isPlainSchemaObject(b) ? resolveLocalSchemaRef(b, schema) : b)).filter((s) => s !== null))]
|
|
511
|
+
: [];
|
|
512
|
+
const verb = key === "oneOf" ? "Provide parameters for exactly one of" : "Provide parameters for at least one of";
|
|
513
|
+
if (summaries.length === 0)
|
|
514
|
+
return `Input constraint: ${verb} the documented parameter groups (flattened from a JSON Schema ${key}).`;
|
|
515
|
+
return `Input constraint: ${verb}: ${summaries.map((s) => `(${s})`).join(" or ")}.`;
|
|
516
|
+
}
|
|
517
|
+
export function normalizeMcpToolSchema(schema) {
|
|
518
|
+
if (!isPlainSchemaObject(schema))
|
|
519
|
+
return { outcome: "unchanged" };
|
|
520
|
+
const combinatorsPresent = MCP_SCHEMA_COMBINATOR_KEYS.filter((k) => k in schema);
|
|
521
|
+
if (combinatorsPresent.length === 0)
|
|
522
|
+
return { outcome: "unchanged" };
|
|
523
|
+
try {
|
|
524
|
+
const properties = Object.create(null);
|
|
525
|
+
const collectProps = (propsObj) => {
|
|
526
|
+
if (!isPlainSchemaObject(propsObj))
|
|
527
|
+
return;
|
|
528
|
+
for (const [name, propSchema] of Object.entries(propsObj)) {
|
|
529
|
+
if (MCP_SCHEMA_PROP_NAME_RE.test(name) && !(name in properties) && isPlainSchemaObject(propSchema)) {
|
|
530
|
+
properties[name] = propSchema;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
collectProps(schema.properties);
|
|
535
|
+
for (const key of combinatorsPresent) {
|
|
536
|
+
const branches = schema[key];
|
|
537
|
+
if (!Array.isArray(branches)) {
|
|
538
|
+
return { outcome: "drop", reason: `input schema has top-level ${key} that is not an array` };
|
|
539
|
+
}
|
|
540
|
+
for (const branch of branches) {
|
|
541
|
+
if (isPlainSchemaObject(branch))
|
|
542
|
+
collectProps(resolveLocalSchemaRef(branch, schema).properties);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
const required = new Set();
|
|
546
|
+
const collectRequired = (reqArr) => {
|
|
547
|
+
if (!Array.isArray(reqArr))
|
|
548
|
+
return;
|
|
549
|
+
for (const name of reqArr) {
|
|
550
|
+
if (typeof name === "string" && name in properties)
|
|
551
|
+
required.add(name);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
collectRequired(schema.required);
|
|
555
|
+
const allOfBranches = schema.allOf;
|
|
556
|
+
if (Array.isArray(allOfBranches)) {
|
|
557
|
+
for (const branch of allOfBranches) {
|
|
558
|
+
if (isPlainSchemaObject(branch))
|
|
559
|
+
collectRequired(resolveLocalSchemaRef(branch, schema).required);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const hasAnyOfOrOneOf = combinatorsPresent.includes("anyOf") || combinatorsPresent.includes("oneOf");
|
|
563
|
+
const flattened = { type: "object", properties, required: [...required] };
|
|
564
|
+
for (const key of MCP_SCHEMA_CARRY_KEYS) {
|
|
565
|
+
if (key in schema)
|
|
566
|
+
flattened[key] = schema[key];
|
|
567
|
+
}
|
|
568
|
+
const note = buildSchemaFlattenNote(combinatorsPresent, schema, hasAnyOfOrOneOf);
|
|
569
|
+
return { outcome: "normalized", schema: flattened, note, combinators: combinatorsPresent };
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
return { outcome: "drop", reason: `input schema uses top-level ${combinatorsPresent.join("/")} and could not be normalized` };
|
|
573
|
+
}
|
|
574
|
+
}
|
|
422
575
|
export function mcpToolSchemaProblem(schema) {
|
|
423
576
|
if (schema === undefined)
|
|
424
577
|
return undefined;
|
|
@@ -512,7 +665,8 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
|
|
|
512
665
|
continue;
|
|
513
666
|
}
|
|
514
667
|
try {
|
|
515
|
-
const listed = await h.client
|
|
668
|
+
const listed = await listToolsLenient(h.client);
|
|
669
|
+
cacheMcpToolMetadata(h.client, listed.tools);
|
|
516
670
|
const { serverTools, serverAxes, dropped } = intakeListedTools(listed, h.spec, h.client, h.health, imageResizer);
|
|
517
671
|
const newNames = serverTools.map((t) => t.name);
|
|
518
672
|
const added = newNames.filter((n) => !h.toolNames.includes(n));
|
|
@@ -600,14 +754,14 @@ function resourceLine(r) {
|
|
|
600
754
|
function serverNames(servers) {
|
|
601
755
|
return servers.map((r) => r.server).join(", ") || "(none)";
|
|
602
756
|
}
|
|
603
|
-
async function readDirViaExtension(rs, uri, signal, timeoutMs) {
|
|
757
|
+
async function readDirViaExtension(rs, uri, signal, timeoutMs, watchdog) {
|
|
604
758
|
const resources = [];
|
|
605
759
|
let cursor;
|
|
606
760
|
let pages = 0;
|
|
607
761
|
do {
|
|
608
762
|
let page;
|
|
609
763
|
try {
|
|
610
|
-
page = await rs.client.request({ method: "resources/directory/read", params: { uri, ...(cursor !== undefined ? { cursor } : {}) } }, ListResourcesResultSchema, { signal, timeout: timeoutMs });
|
|
764
|
+
page = await rs.client.request({ method: "resources/directory/read", params: { uri, ...(cursor !== undefined ? { cursor } : {}) } }, ListResourcesResultSchema, { signal: watchdog.combinedSignal, timeout: timeoutMs });
|
|
611
765
|
}
|
|
612
766
|
catch (err) {
|
|
613
767
|
if (err instanceof McpError && err.code === ErrorCode.InvalidParams) {
|
|
@@ -622,6 +776,7 @@ async function readDirViaExtension(rs, uri, signal, timeoutMs) {
|
|
|
622
776
|
}
|
|
623
777
|
throw err;
|
|
624
778
|
}
|
|
779
|
+
watchdog.rearm();
|
|
625
780
|
resources.push(...page.resources);
|
|
626
781
|
cursor = page.nextCursor;
|
|
627
782
|
pages++;
|
|
@@ -689,8 +844,10 @@ function buildResourceTools(resourceServers) {
|
|
|
689
844
|
sections.push(`[${rs.server}] Error: server disconnected — its resources are unavailable.`);
|
|
690
845
|
continue;
|
|
691
846
|
}
|
|
847
|
+
const idleMs = mcpIdleTimeoutMs(rs.transportKind);
|
|
848
|
+
const watchdog = armMcpIdleWatchdog(rs.health, idleMs, signal);
|
|
692
849
|
try {
|
|
693
|
-
const res = await rs.client.listResources(undefined, { signal, timeout: mcpToolTimeoutMs() });
|
|
850
|
+
const res = await rs.client.listResources(undefined, { signal: watchdog.combinedSignal, timeout: mcpToolTimeoutMs() });
|
|
694
851
|
all.push(...res.resources.map((r) => ({ server: rs.server, ...r })));
|
|
695
852
|
const lines = res.resources.map((r) => resourceLine(r));
|
|
696
853
|
sections.push(`[${rs.server}]\n${lines.length ? lines.join("\n") : "(no resources)"}`);
|
|
@@ -698,10 +855,17 @@ function buildResourceTools(resourceServers) {
|
|
|
698
855
|
catch (err) {
|
|
699
856
|
if (signal?.aborted)
|
|
700
857
|
throw err;
|
|
701
|
-
const msg =
|
|
858
|
+
const msg = watchdog.idleSignal.reason === IDLE_WATCHDOG_ABORT_REASON
|
|
859
|
+
? `received no response for ${idleMs}ms (idle watchdog; set MCP_IDLE_TIMEOUT_STDIO / MCP_IDLE_TIMEOUT_HTTP to change this bound)`
|
|
860
|
+
: err instanceof Error
|
|
861
|
+
? err.message
|
|
862
|
+
: String(err);
|
|
702
863
|
errors.push({ server: rs.server, error: msg });
|
|
703
864
|
sections.push(`[${rs.server}] Error: could not list resources: ${inlineUntrusted(msg)}`);
|
|
704
865
|
}
|
|
866
|
+
finally {
|
|
867
|
+
watchdog.dispose();
|
|
868
|
+
}
|
|
705
869
|
}
|
|
706
870
|
const body = sections.join("\n\n");
|
|
707
871
|
return {
|
|
@@ -739,9 +903,17 @@ function buildResourceTools(resourceServers) {
|
|
|
739
903
|
if (rs.health.dead)
|
|
740
904
|
throwDeadServer(server, what);
|
|
741
905
|
const timeoutMs = mcpToolTimeoutMs();
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
906
|
+
const idleMs = mcpIdleTimeoutMs(rs.transportKind);
|
|
907
|
+
const watchdog = armMcpIdleWatchdog(rs.health, idleMs, signal);
|
|
908
|
+
let res;
|
|
909
|
+
try {
|
|
910
|
+
res = await rs.client
|
|
911
|
+
.readResource({ uri }, { signal: watchdog.combinedSignal, timeout: timeoutMs })
|
|
912
|
+
.catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true, idle: { signal: watchdog.idleSignal, idleMs } }));
|
|
913
|
+
}
|
|
914
|
+
finally {
|
|
915
|
+
watchdog.dispose();
|
|
916
|
+
}
|
|
745
917
|
const text = (await Promise.all((res.contents ?? []).map(async (c) => {
|
|
746
918
|
if ("text" in c && typeof c.text === "string")
|
|
747
919
|
return c.text;
|
|
@@ -783,48 +955,103 @@ function buildResourceTools(resourceServers) {
|
|
|
783
955
|
if (rs.health.dead)
|
|
784
956
|
throwDeadServer(server, what);
|
|
785
957
|
const timeoutMs = mcpToolTimeoutMs();
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
958
|
+
const idleMs = mcpIdleTimeoutMs(rs.transportKind);
|
|
959
|
+
const watchdog = armMcpIdleWatchdog(rs.health, idleMs, signal);
|
|
960
|
+
try {
|
|
961
|
+
const ext = rs.client.getServerCapabilities()?.extensions?.[MCP_SKILLS_EXTENSION];
|
|
962
|
+
if (ext?.directoryRead === true) {
|
|
963
|
+
const r = await readDirViaExtension(rs, uri, signal, timeoutMs, watchdog).catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true, idle: { signal: watchdog.idleSignal, idleMs } }));
|
|
964
|
+
if (r.kind === "not_directory") {
|
|
965
|
+
return {
|
|
966
|
+
content: [{ type: "text", text: `Not a directory resource: ${inlineUntrusted(uri)}. If it is a file resource, use ${READ_MCP_RESOURCE} instead.` }],
|
|
967
|
+
details: { resources: [] },
|
|
968
|
+
terminate: false,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
if (r.kind === "ok")
|
|
972
|
+
return renderDirChildren(server, uri, r.resources, { truncated: r.truncated, incomplete: r.incomplete, cursorInvalid: r.cursorInvalid });
|
|
973
|
+
watchdog.rearm();
|
|
795
974
|
}
|
|
796
|
-
|
|
797
|
-
|
|
975
|
+
const res = await rs.client
|
|
976
|
+
.listResources(undefined, { signal: watchdog.combinedSignal, timeout: timeoutMs })
|
|
977
|
+
.catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true, idle: { signal: watchdog.idleSignal, idleMs } }));
|
|
978
|
+
const children = res.resources.filter((r) => typeof r.uri === "string" && r.uri !== uri && r.uri.startsWith(uri));
|
|
979
|
+
return renderDirChildren(server, uri, children);
|
|
980
|
+
}
|
|
981
|
+
finally {
|
|
982
|
+
watchdog.dispose();
|
|
798
983
|
}
|
|
799
|
-
const res = await rs.client
|
|
800
|
-
.listResources(undefined, { signal, timeout: timeoutMs })
|
|
801
|
-
.catch((err) => rethrowHonestMcpError(err, { server, what, timeoutMs, writeEffect: false, signal, attributeServer: true }));
|
|
802
|
-
const children = res.resources.filter((r) => typeof r.uri === "string" && r.uri !== uri && r.uri.startsWith(uri));
|
|
803
|
-
return renderDirChildren(server, uri, children);
|
|
804
984
|
},
|
|
805
985
|
});
|
|
806
986
|
axes.push({ name: READ_MCP_RESOURCE_DIR, effect: "read" });
|
|
807
987
|
}
|
|
808
988
|
return { tools, axes };
|
|
809
989
|
}
|
|
990
|
+
function cacheMcpToolMetadata(client, tools) {
|
|
991
|
+
const withCache = client;
|
|
992
|
+
if (typeof withCache.cacheToolMetadata !== "function")
|
|
993
|
+
return;
|
|
994
|
+
try {
|
|
995
|
+
withCache.cacheToolMetadata(tools);
|
|
996
|
+
}
|
|
997
|
+
catch {
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
const LenientListToolsResultSchema = {
|
|
1001
|
+
safeParse(data) {
|
|
1002
|
+
if (typeof data !== "object" || data === null)
|
|
1003
|
+
return { success: false, error: new Error("tools/list result is not an object") };
|
|
1004
|
+
const rawTools = data.tools;
|
|
1005
|
+
if (!Array.isArray(rawTools))
|
|
1006
|
+
return { success: false, error: new Error("tools/list result.tools is not an array") };
|
|
1007
|
+
const tools = [];
|
|
1008
|
+
for (const raw of rawTools) {
|
|
1009
|
+
if (typeof raw !== "object" || raw === null || typeof raw.name !== "string") {
|
|
1010
|
+
return { success: false, error: new Error("a tools/list entry is missing a string name") };
|
|
1011
|
+
}
|
|
1012
|
+
const t = raw;
|
|
1013
|
+
tools.push({
|
|
1014
|
+
name: t.name,
|
|
1015
|
+
...(typeof t.description === "string" ? { description: t.description } : {}),
|
|
1016
|
+
inputSchema: t.inputSchema,
|
|
1017
|
+
...(t.annotations !== undefined ? { annotations: t.annotations } : {}),
|
|
1018
|
+
...(t._meta !== undefined ? { _meta: t._meta } : {}),
|
|
1019
|
+
...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
|
|
1020
|
+
...(t.execution !== undefined ? { execution: t.execution } : {}),
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
const nextCursor = data.nextCursor;
|
|
1024
|
+
return { success: true, data: { tools, ...(typeof nextCursor === "string" ? { nextCursor } : {}) } };
|
|
1025
|
+
},
|
|
1026
|
+
};
|
|
1027
|
+
async function listToolsLenient(client, options) {
|
|
1028
|
+
return client.request({ method: "tools/list", params: {} }, LenientListToolsResultSchema, options);
|
|
1029
|
+
}
|
|
810
1030
|
async function connectServer(spec, principal, onElicit, imageResizer) {
|
|
811
1031
|
const elicitOn = spec.elicitation === true && onElicit !== undefined;
|
|
1032
|
+
const health = { dead: false, pendingElicitations: 0, lastElicitationClosedAt: 0 };
|
|
812
1033
|
const client = new Client({ name: `sema-core/${spec.name}`, version: "0.1.0" }, { capabilities: elicitOn ? { elicitation: { form: {} } } : {} });
|
|
813
1034
|
if (elicitOn) {
|
|
814
1035
|
client.setRequestHandler(ElicitRequestSchema, async (request, extra) => {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1036
|
+
health.pendingElicitations++;
|
|
1037
|
+
try {
|
|
1038
|
+
const p = request.params;
|
|
1039
|
+
const req = {
|
|
1040
|
+
server: spec.name,
|
|
1041
|
+
message: typeof p.message === "string" ? p.message : "",
|
|
1042
|
+
...(p.requestedSchema !== undefined ? { requestedSchema: p.requestedSchema } : {}),
|
|
1043
|
+
...(principal !== undefined ? { principal } : {}),
|
|
1044
|
+
};
|
|
1045
|
+
const answer = await onElicit(req, extra?.signal);
|
|
1046
|
+
return answer.action === "accept" ? { action: "accept", content: answer.content ?? {} } : { action: answer.action };
|
|
1047
|
+
}
|
|
1048
|
+
finally {
|
|
1049
|
+
health.pendingElicitations--;
|
|
1050
|
+
health.lastElicitationClosedAt = Date.now();
|
|
1051
|
+
}
|
|
824
1052
|
});
|
|
825
1053
|
}
|
|
826
1054
|
const transport = buildTransport(spec, principal);
|
|
827
|
-
const health = { dead: false };
|
|
828
1055
|
const announce = {};
|
|
829
1056
|
const startupMs = mcpStartupTimeoutMs();
|
|
830
1057
|
const startupOpts = startupMs !== undefined ? { timeout: startupMs } : undefined;
|
|
@@ -834,7 +1061,8 @@ async function connectServer(spec, principal, onElicit, imageResizer) {
|
|
|
834
1061
|
health.dead = true;
|
|
835
1062
|
announce.fn?.();
|
|
836
1063
|
};
|
|
837
|
-
const listed = await client
|
|
1064
|
+
const listed = await listToolsLenient(client, startupOpts);
|
|
1065
|
+
cacheMcpToolMetadata(client, listed.tools);
|
|
838
1066
|
const { serverTools, serverAxes, dropped } = intakeListedTools(listed, spec, client, health, imageResizer);
|
|
839
1067
|
const caps = client.getServerCapabilities();
|
|
840
1068
|
const resourceInfo = caps?.resources
|
|
@@ -860,7 +1088,7 @@ async function connectServer(spec, principal, onElicit, imageResizer) {
|
|
|
860
1088
|
axes: serverAxes,
|
|
861
1089
|
dropped,
|
|
862
1090
|
...(instructions ? { instructions } : {}),
|
|
863
|
-
...(resourceInfo && (resourceInfo.listAllowed || resourceInfo.readAllowed) ? { resourceServer: { server: spec.name, client, health, ...resourceInfo } } : {}),
|
|
1091
|
+
...(resourceInfo && (resourceInfo.listAllowed || resourceInfo.readAllowed) ? { resourceServer: { server: spec.name, client, health, transportKind: idleKindOf(spec), ...resourceInfo } } : {}),
|
|
864
1092
|
};
|
|
865
1093
|
}
|
|
866
1094
|
catch (err) {
|
|
@@ -876,7 +1104,18 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
|
|
|
876
1104
|
if (spec.allowTools && !spec.allowTools.includes(t.name)) {
|
|
877
1105
|
continue;
|
|
878
1106
|
}
|
|
879
|
-
const
|
|
1107
|
+
const normalized = normalizeMcpToolSchema(t.inputSchema);
|
|
1108
|
+
let effectiveInputSchema = t.inputSchema;
|
|
1109
|
+
let effectiveDescription = t.description;
|
|
1110
|
+
if (normalized.outcome === "drop") {
|
|
1111
|
+
dropped.push({ tool: inlineUntrusted(t.name), reason: inlineUntrusted(normalized.reason, 240) });
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (normalized.outcome === "normalized") {
|
|
1115
|
+
effectiveInputSchema = normalized.schema;
|
|
1116
|
+
effectiveDescription = normalized.note + (t.description ? `\n\n${t.description}` : "");
|
|
1117
|
+
}
|
|
1118
|
+
const schemaProblem = mcpToolSchemaProblem(effectiveInputSchema);
|
|
880
1119
|
if (schemaProblem !== undefined) {
|
|
881
1120
|
dropped.push({ tool: inlineUntrusted(t.name), reason: inlineUntrusted(schemaProblem, 240) });
|
|
882
1121
|
continue;
|
|
@@ -898,24 +1137,32 @@ function intakeListedTools(listed, spec, client, health, imageResizer) {
|
|
|
898
1137
|
const mcpMaxResultSizeChars = resolveMcpDeclaredResultSize(t._meta);
|
|
899
1138
|
serverTools.push({
|
|
900
1139
|
name: namespacedName,
|
|
901
|
-
description:
|
|
1140
|
+
description: effectiveDescription ?? `MCP tool ${remoteName} from ${spec.name}`,
|
|
902
1141
|
label: `${spec.name}:${remoteName}`,
|
|
903
|
-
parameters: (
|
|
1142
|
+
parameters: (effectiveInputSchema ?? { type: "object" }),
|
|
904
1143
|
...(mcpMaxResultSizeChars !== undefined ? { mcpMaxResultSizeChars } : {}),
|
|
905
1144
|
execute: async (_toolCallId, params, signal) => {
|
|
906
1145
|
const what = `The call to tool ${inlineUntrusted(remoteName)}`;
|
|
907
1146
|
if (health.dead)
|
|
908
1147
|
throwDeadServer(spec.name, what);
|
|
909
1148
|
const timeoutMs = mcpToolTimeoutMs();
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1149
|
+
const idleMs = mcpIdleTimeoutMs(idleKindOf(spec));
|
|
1150
|
+
const watchdog = armMcpIdleWatchdog(health, idleMs, signal);
|
|
1151
|
+
let res;
|
|
1152
|
+
try {
|
|
1153
|
+
res = await client
|
|
1154
|
+
.callTool({ name: remoteName, arguments: (params ?? {}) }, undefined, {
|
|
1155
|
+
signal: watchdog.combinedSignal,
|
|
1156
|
+
timeout: timeoutMs,
|
|
1157
|
+
resetTimeoutOnProgress: true,
|
|
1158
|
+
onprogress: () => watchdog.rearm(),
|
|
1159
|
+
maxTotalTimeout: mcpToolTotalTimeoutMs(timeoutMs),
|
|
1160
|
+
})
|
|
1161
|
+
.catch((err) => rethrowHonestMcpError(err, { server: spec.name, what, timeoutMs, writeEffect, signal, idle: { signal: watchdog.idleSignal, idleMs } }));
|
|
1162
|
+
}
|
|
1163
|
+
finally {
|
|
1164
|
+
watchdog.dispose();
|
|
1165
|
+
}
|
|
919
1166
|
const mapped = await mapContent(res.content, spec.name, imageResizer);
|
|
920
1167
|
if (res.isError === true) {
|
|
921
1168
|
const parts = [];
|
|
@@ -194,6 +194,10 @@ export interface Prepared {
|
|
|
194
194
|
promptOverheadTokens: number;
|
|
195
195
|
readTaskFile?: (path: string) => Promise<string | null>;
|
|
196
196
|
recentlyReadFiles?: () => string[];
|
|
197
|
+
onCompactionApplied?: (attachedComplete: ReadonlyArray<{
|
|
198
|
+
path: string;
|
|
199
|
+
content: string;
|
|
200
|
+
}>) => void;
|
|
197
201
|
lspDiagnostics?: {
|
|
198
202
|
registry: import("../lsp-diagnostics.js").LspDiagnosticsRegistry;
|
|
199
203
|
nudge: (rawPath: string) => void;
|
|
@@ -55,7 +55,7 @@ import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.j
|
|
|
55
55
|
import { createTaskOutputTool, createTaskStopTool, defaultTaskRegistry } from "../task-registry.js";
|
|
56
56
|
import { createMonitorTool } from "../../tools/monitor.js";
|
|
57
57
|
import { createWorktreeTools } from "../../tools/worktree.js";
|
|
58
|
-
import { bashReversibilityProbe, createHandsToolkit, seedReadFileStateFromContext, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
58
|
+
import { applyCompactionToReadFileState, bashReversibilityProbe, createHandsToolkit, seedReadFileStateFromContext, HAND_TOOL_EFFECTS, pdfModelCapabilitiesOf } from "../../tools/fs/index.js";
|
|
59
59
|
import { decodeTextBytes } from "../../tools/fs/encoding.js";
|
|
60
60
|
import { ASK_USER_QUESTION_TOOL_NAME, createAskUserQuestionTool } from "../ask-question.js";
|
|
61
61
|
import { createSchedulerTools } from "../../tools/scheduler-tools.js";
|
|
@@ -3286,9 +3286,13 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
3286
3286
|
: undefined;
|
|
3287
3287
|
const recentlyReadFiles = handsEnabled && readFileStateForCheckpoint
|
|
3288
3288
|
? () => [...readFileStateForCheckpoint.entries()]
|
|
3289
|
+
.filter(([, v]) => v.seededFromContext !== true)
|
|
3289
3290
|
.sort((a, b) => (b[1].lastReadAt ?? 0) - (a[1].lastReadAt ?? 0))
|
|
3290
3291
|
.map(([path]) => path)
|
|
3291
3292
|
: undefined;
|
|
3293
|
+
const onCompactionApplied = handsEnabled && readFileStateForCheckpoint
|
|
3294
|
+
? (attachedComplete) => applyCompactionToReadFileState(readFileStateForCheckpoint, attachedComplete)
|
|
3295
|
+
: undefined;
|
|
3292
3296
|
const detectExternalChanges = handsEnabled && readFileStateForCheckpoint
|
|
3293
3297
|
? async (maxFiles) => {
|
|
3294
3298
|
const candidates = [...readFileStateForCheckpoint.entries()]
|
|
@@ -3371,7 +3375,7 @@ export async function prepareTask(spec, deps, sessions, resume, _memorySelector,
|
|
|
3371
3375
|
: undefined;
|
|
3372
3376
|
overheadState.promptChars = systemPrompt.length;
|
|
3373
3377
|
const preparedHolder = {};
|
|
3374
|
-
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ownedEnv, suspendRef, reviewRef, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3378
|
+
const buildPrepared = () => ({ harness, session, sessionId, taskRootPath, model, thinking, compModel, mcp: mcp, blockedRef, outputRef, abortController, conflictRef, blockedToolCalls, nestedStats, cwdRef: handsCwdRef, ...(worktreeSessionRef !== undefined ? { worktreeSessionRef } : {}), ...(workspaceStateSettle !== undefined ? { workspaceStateSettle } : {}), denyNarrowingPolicy, ...(basePolicyForResumeEdit !== undefined ? { basePolicyForResumeEdit } : {}), releaseSignal, cacheBreakDetector, cacheFingerprint, promptManifest, epochDeclaredSections, activeTools, ownedEnv, suspendRef, reviewRef, reviewRequestRef, suspendLoopRef, suspendForResource, ...(callCapRef ? { callCapRef } : {}), ...(cutKills ? { cutKills } : {}), ...(callCapRef ? { callIssuedAtRef } : {}), suspendForReview, resourceLedger: priorLedger, liveSpendRef, humanReviewRef, now, tools, toolEffects, promptOverheadTokens, readTaskFile, recentlyReadFiles, ...(onCompactionApplied ? { onCompactionApplied } : {}), compactionReuseRef, trimPressureRef, ...(memoryEngineSession ? { memoryEngineSession } : {}), ...(subagentRetain ? { subagentRetain } : {}), ...(lspDiagnostics && nudgeLspOnEdit ? { lspDiagnostics: { registry: lspDiagnostics, nudge: nudgeLspOnEdit } } : {}), planModeRef, ...(dateChange ? { dateChange } : {}), ...(instructionSources ? { instructionSources } : {}), ...(detectExternalChanges ? { detectExternalChanges } : {}), ...(toolsDeltaRef ? { toolsDeltaRef } : {}), ...(agentListing ? { agentListing } : {}), ...(skillsListing ? { skillsListing } : {}), announcedListingsRef, listBackgroundTasks, ...(turnSnapshotRef.current !== undefined ? { turnSnapshot: turnSnapshotRef.current } : {}), ...(centerCompactionCandidate !== undefined ? { centerCompactionCandidate } : {}) });
|
|
3375
3379
|
const prepared = buildPrepared();
|
|
3376
3380
|
preparedHolder.current = prepared;
|
|
3377
3381
|
return prepared;
|
|
@@ -1826,6 +1826,7 @@ export class Runner {
|
|
|
1826
1826
|
...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
|
|
1827
1827
|
}
|
|
1828
1828
|
: undefined,
|
|
1829
|
+
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
1829
1830
|
...this.seamCCompactionOptions(prepared),
|
|
1830
1831
|
...windowSafetyOptions(prepared.harness.getModel()),
|
|
1831
1832
|
...this.compactionHookOptions(spec, prepared.sessionId, "forced"),
|
|
@@ -2259,6 +2260,7 @@ export class Runner {
|
|
|
2259
2260
|
...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
|
|
2260
2261
|
}
|
|
2261
2262
|
: undefined,
|
|
2263
|
+
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
2262
2264
|
...this.seamCCompactionOptions(prepared),
|
|
2263
2265
|
...windowSafetyOptions(event.model),
|
|
2264
2266
|
...this.compactionHookOptions(spec, prepared.sessionId, trimPressure ? "forced" : forceManual ? "manual" : "auto"),
|
|
@@ -3540,6 +3542,7 @@ export class Runner {
|
|
|
3540
3542
|
...(typeof spec.compaction?.attachWorkingFiles === "object" ? spec.compaction.attachWorkingFiles : undefined),
|
|
3541
3543
|
}
|
|
3542
3544
|
: undefined,
|
|
3545
|
+
...(prepared.onCompactionApplied ? { onApplied: prepared.onCompactionApplied } : {}),
|
|
3543
3546
|
...this.seamCCompactionOptions(prepared),
|
|
3544
3547
|
...this.compactionHookOptions(spec, prepared.sessionId, "auto"),
|
|
3545
3548
|
});
|
package/dist/tools/fs/index.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export type ReadImageDownsamplerOption = ImageDownsampler | false | undefined;
|
|
|
10
10
|
export declare function msTimeoutToSec(timeoutMs: number | undefined): number;
|
|
11
11
|
export declare function seededFileUnchangedReminder(filePath: string): string;
|
|
12
12
|
export declare function seedReadFileStateFromContext(state: ReadFileState, key: string, content: string): void;
|
|
13
|
+
export declare function applyCompactionToReadFileState(state: ReadFileState, attachedComplete: ReadonlyArray<{
|
|
14
|
+
path: string;
|
|
15
|
+
content: string;
|
|
16
|
+
}>): void;
|
|
13
17
|
export interface CwdRef {
|
|
14
18
|
current: string;
|
|
15
19
|
}
|
package/dist/tools/fs/index.js
CHANGED
|
@@ -73,6 +73,17 @@ export function seededFileUnchangedReminder(filePath) {
|
|
|
73
73
|
export function seedReadFileStateFromContext(state, key, content) {
|
|
74
74
|
state.set(key, { hash: sha256(content), totalLines: countLines(content), truncated: false, lastReadAt: Date.now(), seededFromContext: true });
|
|
75
75
|
}
|
|
76
|
+
export function applyCompactionToReadFileState(state, attachedComplete) {
|
|
77
|
+
for (const [k, v] of [...state]) {
|
|
78
|
+
if (v.seededFromContext !== true)
|
|
79
|
+
state.delete(k);
|
|
80
|
+
}
|
|
81
|
+
for (const f of attachedComplete) {
|
|
82
|
+
const totalLines = countLines(f.content);
|
|
83
|
+
const displayLines = f.content.length === 0 ? 0 : f.content.split("\n").length;
|
|
84
|
+
state.set(f.path, { hash: sha256(f.content), totalLines, truncated: false, view: { start: 1, end: displayLines }, lastReadAt: Date.now() });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
76
87
|
async function enoentMessage(env, key, cwd, signal) {
|
|
77
88
|
let msg = `File does not exist. Note: your current working directory is ${cwd}.`;
|
|
78
89
|
const sep = Math.max(key.lastIndexOf("/"), key.lastIndexOf("\\"));
|