tinker-agent 2.7.0 → 2.9.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +55 -1
  2. package/README.md +64 -10
  3. package/package.json +4 -3
  4. package/src/agent/runtime-context-capabilities.ts +19 -0
  5. package/src/agent/runtime-context-events.ts +127 -0
  6. package/src/agent/runtime-context-maintenance.ts +780 -0
  7. package/src/agent/runtime-interactions.ts +291 -0
  8. package/src/agent/runtime-prompt-scheduler.ts +182 -0
  9. package/src/agent/runtime-session-contracts.ts +317 -0
  10. package/src/agent/runtime-session.ts +253 -2117
  11. package/src/agent/runtime-skills.ts +544 -0
  12. package/src/cli/runner-dependencies.ts +6 -5
  13. package/src/context/context-automation-policy.ts +12 -118
  14. package/src/events/types.ts +13 -1
  15. package/src/memory/memory-get-tool.ts +1 -1
  16. package/src/observation/observation-builder.ts +41 -11
  17. package/src/session/resume-projection.ts +47 -21
  18. package/src/session/session-history-access.ts +238 -0
  19. package/src/session/session-store-context-readers.ts +183 -0
  20. package/src/session/session-store-ledger-writer.ts +315 -0
  21. package/src/session/session-store-record-writer.ts +318 -0
  22. package/src/session/session-store-recovery.ts +225 -0
  23. package/src/session/session-store-revisions.ts +1004 -0
  24. package/src/session/session-store-sql.ts +40 -0
  25. package/src/session/session-store-validation.ts +657 -0
  26. package/src/session/session-store.ts +756 -3186
  27. package/src/tools/bash-task.ts +20 -2
  28. package/src/tools/bash.ts +1 -1
  29. package/src/tools/context-maintenance.ts +1 -1
  30. package/src/tools/read.ts +1 -1
  31. package/src/tools/recall.ts +106 -50
  32. package/src/tools/registry.ts +4 -6
  33. package/src/tools/task-output-range.ts +146 -0
  34. package/src/tools/task-output-tool.ts +35 -5
  35. package/src/tools/task-output.ts +35 -0
  36. package/src/tools/task-tool-args.ts +34 -0
  37. package/src/tools/types.ts +9 -0
  38. package/src/tools/wait.ts +1 -3
  39. package/src/tui/event-store.ts +8 -3
@@ -0,0 +1,544 @@
1
+ import {
2
+ canonicalSequenceHash,
3
+ renderedMessageHash,
4
+ } from "../context/compiled-context-hash";
5
+ import {
6
+ commitAgentSkillsContextUpdate,
7
+ ContextManagerError,
8
+ } from "../context/context-manager";
9
+ import type { BuiltContextRequest } from "../context/context-revision";
10
+ import { ContextRevisionCompiler } from "../context/context-revision-compiler";
11
+ import {
12
+ changedContextSurfaceComponents,
13
+ contextSurfaceChangeManifestHash,
14
+ contextSurfaceChanges,
15
+ createContextSurface,
16
+ sameContextSurface,
17
+ type StoredContextSurfaceV8,
18
+ } from "../context/context-surface";
19
+ import type { ToolCompletionInput } from "../context/protocol-frame";
20
+ import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
21
+ import type { AgentEventInput } from "../events/types";
22
+ import { type RuntimeIdFactory, type SessionId } from "../ids/runtime-id";
23
+ import { SessionError } from "../session/session-errors";
24
+ import type { SessionStore, StoredSkillActivation } from "../session/session-store";
25
+ import {
26
+ activeSkillManifestEntry,
27
+ skillCatalogManifest,
28
+ } from "../skills/skill-catalog";
29
+ import {
30
+ buildActiveSystemPrompt,
31
+ renderSkillActivationReceipt,
32
+ SkillActivationCoordinator,
33
+ } from "../skills/skill-context";
34
+ import type { SkillCatalogSnapshot } from "../skills/skill-loader";
35
+ import { type DefaultTooling } from "../tools/registry";
36
+ import type { ContextMeter } from "./context-meter";
37
+ import {
38
+ assertPreparedMatchesSurface,
39
+ boundedContextErrorCode,
40
+ elapsedMs,
41
+ } from "./runtime-context-events";
42
+ import {
43
+ type ContextSurfaceRefreshSummary,
44
+ type CreateRuntimeSessionInput,
45
+ type RuntimeSkillsSnapshot,
46
+ type SkillsUpdateSummary,
47
+ } from "./runtime-session-contracts";
48
+ import type { CommittedToolCompletion } from "./session-ledger";
49
+ import type { IterationIdentity } from "./types";
50
+
51
+ /** Coordinates skill activation and the corresponding persisted context surface. */
52
+ export class RuntimeSkills {
53
+ private skillCoordinator = new SkillActivationCoordinator();
54
+ get coordinator(): SkillActivationCoordinator {
55
+ return this.skillCoordinator;
56
+ }
57
+
58
+ restoreCoordinator(coordinator: SkillActivationCoordinator): void {
59
+ this.skillCoordinator = coordinator;
60
+ }
61
+
62
+ constructor(
63
+ private readonly sessionId: SessionId,
64
+ private readonly store: SessionStore,
65
+ private readonly input: Pick<
66
+ CreateRuntimeSessionInput,
67
+ "systemPrompt" | "projectInstruction" | "modelClient"
68
+ >,
69
+ private readonly skillCatalog: SkillCatalogSnapshot,
70
+ private readonly idFactory: RuntimeIdFactory,
71
+ private readonly contextMeter: ContextMeter,
72
+ private readonly toolDefinitions: DefaultTooling["registry"]["definitions"],
73
+ private readonly append: (event: AgentEventInput) => Promise<void>,
74
+ ) {}
75
+
76
+ async refreshContextSurface(
77
+ candidateSurface: StoredContextSurfaceV8,
78
+ ): Promise<ContextSurfaceRefreshSummary | undefined> {
79
+ const snapshot = this.store.loadContextSnapshot();
80
+ if (sameContextSurface(snapshot.surface, candidateSurface)) {
81
+ return undefined;
82
+ }
83
+
84
+ const changes = contextSurfaceChanges(snapshot.surface, candidateSurface);
85
+ const changed = changedContextSurfaceComponents(changes);
86
+ if (changed.length === 0) {
87
+ throw new Error("Changed context surface has an empty change manifest.");
88
+ }
89
+ const startedAt = performance.now();
90
+ await this.append({
91
+ type: "context.revision.started",
92
+ sessionId: this.sessionId,
93
+ data: {
94
+ strategy: "surface_refresh",
95
+ reason: "resume",
96
+ baseRevisionNumber: snapshot.revision.revisionNumber,
97
+ changed,
98
+ },
99
+ });
100
+
101
+ let stage: "prepare" | "commit" | "activate" = "prepare";
102
+ let committed = false;
103
+ try {
104
+ const compiler = new ContextRevisionCompiler();
105
+ const active = compiler.compileActive(snapshot);
106
+ const candidateCompiled = compiler.compileProspective({
107
+ active,
108
+ canonical: snapshot.canonical,
109
+ activeOverrides: snapshot.activeOverrides,
110
+ addedOverrides: [],
111
+ activeSurface: snapshot.surface,
112
+ surface: candidateSurface,
113
+ });
114
+ const prepared = this.input.modelClient.prepare({
115
+ messages: candidateCompiled.entries.map((entry) => entry.message),
116
+ tools: [...candidateSurface.toolDefinitions],
117
+ });
118
+ assertPreparedMatchesSurface(prepared, candidateSurface);
119
+
120
+ stage = "commit";
121
+ const revision = this.store.commitSurfaceRefresh({
122
+ revisionId: this.idFactory.createContextRevisionId(),
123
+ expectedBaseRevisionId: snapshot.revision.revisionId,
124
+ expectedBaseRevisionNumber: snapshot.revision.revisionNumber,
125
+ expectedCanonicalThroughOrdinal: snapshot.canonical.messages.length,
126
+ expectedBaseActiveOverrideManifestSha256:
127
+ snapshot.revision.activeOverrideManifestSha256,
128
+ surface: candidateSurface,
129
+ changes,
130
+ changeManifestSha256: contextSurfaceChangeManifestHash(changes),
131
+ canonicalSequenceSha256: canonicalSequenceHash(snapshot.canonical),
132
+ renderedMessageSha256: renderedMessageHash(candidateCompiled.entries),
133
+ });
134
+ committed = true;
135
+
136
+ stage = "activate";
137
+ this.contextMeter.startRevision({
138
+ reason: "context_rebuilt",
139
+ requestConfigHash: prepared.requestConfigHash,
140
+ toolSchemaHash: prepared.toolSchemaHash,
141
+ });
142
+ const summary = Object.freeze({
143
+ previousRevisionNumber: snapshot.revision.revisionNumber,
144
+ revisionNumber: revision.revisionNumber,
145
+ changed,
146
+ toolCountBefore: snapshot.surface.toolDefinitions.length,
147
+ toolCountAfter: candidateSurface.toolDefinitions.length,
148
+ });
149
+ await this.append({
150
+ type: "context.revision.finished",
151
+ sessionId: this.sessionId,
152
+ data: {
153
+ strategy: "surface_refresh",
154
+ reason: "resume",
155
+ baseRevisionNumber: summary.previousRevisionNumber,
156
+ revisionNumber: summary.revisionNumber,
157
+ changed: summary.changed,
158
+ toolCountBefore: summary.toolCountBefore,
159
+ toolCountAfter: summary.toolCountAfter,
160
+ measuredAnchorCleared: true,
161
+ durationMs: elapsedMs(startedAt),
162
+ },
163
+ });
164
+ return summary;
165
+ } catch (error) {
166
+ await this.append({
167
+ type: "context.revision.failed",
168
+ sessionId: this.sessionId,
169
+ data: {
170
+ strategy: "surface_refresh",
171
+ reason: "resume",
172
+ stage,
173
+ errorCode: boundedContextErrorCode(
174
+ error instanceof SessionError
175
+ ? error.code
176
+ : error instanceof Error
177
+ ? error.name
178
+ : "CONTEXT_SURFACE_REFRESH_FAILED",
179
+ ),
180
+ error: `Context surface refresh failed at ${stage}.`,
181
+ committed,
182
+ },
183
+ }).catch(() => undefined);
184
+ throw error;
185
+ }
186
+ }
187
+
188
+ skills(): RuntimeSkillsSnapshot {
189
+ const activeNames = new Set(
190
+ this.skillCoordinator.activeEntries().map((entry) => entry.skill.name),
191
+ );
192
+ return Object.freeze({
193
+ skills: Object.freeze(
194
+ [...this.skillCatalog.skills.values()]
195
+ .sort((left, right) => compareText(left.name, right.name))
196
+ .map((skill) =>
197
+ Object.freeze({
198
+ name: skill.name,
199
+ description: skill.description,
200
+ scope: skill.scope,
201
+ active: activeNames.has(skill.name),
202
+ }),
203
+ ),
204
+ ),
205
+ shadowedNames: Object.freeze(
206
+ this.skillCatalog.shadowed.map((entry) => entry.name),
207
+ ),
208
+ });
209
+ }
210
+
211
+ appendSkillsCatalogLoaded(): Promise<void> {
212
+ const activeNames = this.skillCoordinator
213
+ .activeEntries()
214
+ .map((entry) => entry.skill.name);
215
+ if (
216
+ this.skillCatalog.skills.size === 0 &&
217
+ activeNames.length === 0 &&
218
+ this.skillCatalog.shadowed.length === 0
219
+ ) {
220
+ return Promise.resolve();
221
+ }
222
+ const skills = [...this.skillCatalog.skills.values()];
223
+ return this.append({
224
+ type: "skills.catalog.loaded",
225
+ sessionId: this.sessionId,
226
+ data: {
227
+ availableCount: skills.length,
228
+ projectCount: skills.filter((skill) => skill.scope === "project").length,
229
+ userCount: skills.filter((skill) => skill.scope === "user").length,
230
+ activeNames: Object.freeze(activeNames),
231
+ shadowedNames: Object.freeze(
232
+ this.skillCatalog.shadowed.map((entry) => entry.name),
233
+ ),
234
+ },
235
+ });
236
+ }
237
+
238
+ onToolCompletionsCommitted(input: {
239
+ completions: readonly ToolCompletionInput[];
240
+ committed: readonly CommittedToolCompletion[];
241
+ }): void {
242
+ if (input.completions.length !== input.committed.length) {
243
+ throw new Error("Committed tool completion identity count does not match.");
244
+ }
245
+ for (let index = 0; index < input.completions.length; index += 1) {
246
+ const completion = input.completions[index];
247
+ const committed = input.committed[index];
248
+ if (
249
+ completion === undefined ||
250
+ committed === undefined ||
251
+ completion.call.toolCallId !== committed.toolCallId
252
+ ) {
253
+ throw new Error("Committed tool completion identity is invalid.");
254
+ }
255
+ if (
256
+ completion.kind === "returned" &&
257
+ completion.raw.kind === "skill" &&
258
+ completion.raw.ok &&
259
+ completion.raw.status === "loaded"
260
+ ) {
261
+ this.skillCoordinator.markPending(completion.raw.name);
262
+ }
263
+ }
264
+ }
265
+
266
+ async commitSkillSettlements(input: {
267
+ reason: "activation" | "resume";
268
+ unresolved: readonly StoredSkillActivation[];
269
+ candidateSurface?: StoredContextSurfaceV8;
270
+ activated?: readonly string[];
271
+ refreshed?: readonly string[];
272
+ deactivated?: readonly string[];
273
+ }): Promise<SkillsUpdateSummary> {
274
+ if (input.unresolved.length === 0) {
275
+ throw new Error("Agent Skills update requires unresolved activations.");
276
+ }
277
+ const snapshot = this.store.loadContextSnapshot();
278
+ const canonicalMessages = new Map(
279
+ snapshot.canonical.messages.map((message) => [message.messageId, message]),
280
+ );
281
+ const activeByName = new Map(
282
+ this.skillCoordinator
283
+ .activeEntries()
284
+ .map((entry) => [entry.skill.name, entry] as const),
285
+ );
286
+ const activated = new Set(input.activated ?? []);
287
+ const unavailable = new Set<string>();
288
+ const settlements: Array<{
289
+ activationMessageId: StoredSkillActivation["activationMessageId"];
290
+ name: string;
291
+ state: "promoted" | "rejected";
292
+ rejectionReason?: string;
293
+ }> = [];
294
+ const receipts = [];
295
+ for (const activation of [...input.unresolved].sort((left, right) =>
296
+ compareText(left.name, right.name),
297
+ )) {
298
+ const skill = this.skillCatalog.skills.get(activation.name);
299
+ const canPromote = activation.state === "dispatched" && skill !== undefined;
300
+ if (canPromote) {
301
+ const existing = activeByName.get(activation.name);
302
+ if (
303
+ existing !== undefined &&
304
+ existing.activationMessageId !== activation.activationMessageId
305
+ ) {
306
+ throw new Error(
307
+ `Agent Skill ${activation.name} already has another active activation.`,
308
+ );
309
+ }
310
+ activeByName.set(activation.name, {
311
+ skill,
312
+ activationMessageId: activation.activationMessageId,
313
+ });
314
+ activated.add(activation.name);
315
+ }
316
+ const state = canPromote ? "promoted" : "rejected";
317
+ const rejectionReason =
318
+ state === "promoted"
319
+ ? undefined
320
+ : activation.state === "pending"
321
+ ? "not_dispatched"
322
+ : "unavailable";
323
+ if (rejectionReason === "unavailable") {
324
+ unavailable.add(activation.name);
325
+ }
326
+ settlements.push({
327
+ activationMessageId: activation.activationMessageId,
328
+ name: activation.name,
329
+ state,
330
+ ...(rejectionReason === undefined ? {} : { rejectionReason }),
331
+ });
332
+ const message = canonicalMessages.get(activation.activationMessageId);
333
+ if (message?.role !== "tool") {
334
+ throw new Error(
335
+ `Agent Skill activation message ${activation.activationMessageId} is missing.`,
336
+ );
337
+ }
338
+ receipts.push(
339
+ renderSkillActivationReceipt({
340
+ message: {
341
+ messageId: message.messageId,
342
+ frameId: message.frameId,
343
+ ordinal: message.ordinal,
344
+ content: message.displayText,
345
+ contentSha256: message.contentSha256,
346
+ },
347
+ name: activation.name,
348
+ outcome:
349
+ state === "promoted"
350
+ ? "promoted"
351
+ : rejectionReason === "unavailable"
352
+ ? "unavailable"
353
+ : "rejected",
354
+ }),
355
+ );
356
+ }
357
+ const nextActive = Object.freeze(
358
+ [...activeByName.values()].sort((left, right) =>
359
+ compareText(left.skill.name, right.skill.name),
360
+ ),
361
+ );
362
+ const createdAt = new Date().toISOString();
363
+ const definitions = this.toolDefinitions();
364
+ const renderedSystemPrompt = buildActiveSystemPrompt({
365
+ baseSystemPrompt: this.input.systemPrompt,
366
+ activeSkills: nextActive,
367
+ });
368
+ const surfacePrepared = this.input.modelClient.prepare({
369
+ messages: [{ role: "system", content: renderedSystemPrompt }],
370
+ tools: definitions,
371
+ });
372
+ const generatedSurface =
373
+ input.candidateSurface ??
374
+ createContextSurface({
375
+ surfaceId: this.idFactory.createContextSurfaceId(),
376
+ sessionId: this.sessionId,
377
+ systemPrompt: renderedSystemPrompt,
378
+ recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
379
+ ...(this.input.projectInstruction === undefined
380
+ ? {}
381
+ : { projectInstruction: this.input.projectInstruction }),
382
+ skillCatalog: skillCatalogManifest(this.skillCatalog.skills.values()),
383
+ activeSkills: nextActive.map((entry) =>
384
+ activeSkillManifestEntry(entry.skill, entry.activationMessageId),
385
+ ),
386
+ toolDefinitions: definitions,
387
+ prepared: surfacePrepared,
388
+ createdAt,
389
+ });
390
+ assertPreparedMatchesSurface(surfacePrepared, generatedSurface);
391
+ const surface = sameContextSurface(snapshot.surface, generatedSurface)
392
+ ? snapshot.surface
393
+ : generatedSurface;
394
+ const startedAt = performance.now();
395
+ await this.append({
396
+ type: "context.revision.started",
397
+ sessionId: this.sessionId,
398
+ data: {
399
+ strategy: "skills_update",
400
+ reason: input.reason,
401
+ baseRevisionNumber: snapshot.revision.revisionNumber,
402
+ names: Object.freeze(
403
+ input.unresolved.map((entry) => entry.name).sort(compareText),
404
+ ),
405
+ },
406
+ });
407
+ let stage: "prepare" | "commit" | "activate" = "prepare";
408
+ let committed = false;
409
+ try {
410
+ const revision = commitAgentSkillsContextUpdate({
411
+ store: this.store,
412
+ contextMeter: this.contextMeter,
413
+ idFactory: this.idFactory,
414
+ snapshot,
415
+ surface,
416
+ addedOverrides: receipts,
417
+ settlements,
418
+ });
419
+ committed = true;
420
+ stage = "activate";
421
+ this.skillCoordinator.replaceActive(nextActive);
422
+ this.skillCoordinator.settle(
423
+ input.unresolved.map((activation) => activation.name),
424
+ );
425
+ const summary = Object.freeze({
426
+ previousRevisionNumber: snapshot.revision.revisionNumber,
427
+ revisionNumber: revision.revisionNumber,
428
+ activated: Object.freeze([...activated].sort()),
429
+ refreshed: Object.freeze([...(input.refreshed ?? [])].sort()),
430
+ deactivated: Object.freeze([...(input.deactivated ?? [])].sort()),
431
+ unavailable: Object.freeze([...unavailable].sort()),
432
+ addedOverrideCount: receipts.length,
433
+ });
434
+ await this.append({
435
+ type: "context.revision.finished",
436
+ sessionId: this.sessionId,
437
+ data: {
438
+ strategy: "skills_update",
439
+ reason: input.reason,
440
+ baseRevisionNumber: summary.previousRevisionNumber,
441
+ revisionNumber: summary.revisionNumber,
442
+ activated: summary.activated,
443
+ refreshed: summary.refreshed,
444
+ deactivated: summary.deactivated,
445
+ unavailable: summary.unavailable,
446
+ addedOverrideCount: summary.addedOverrideCount,
447
+ measuredAnchorCleared: true,
448
+ durationMs: elapsedMs(startedAt),
449
+ },
450
+ });
451
+ return summary;
452
+ } catch (error) {
453
+ if (error instanceof ContextManagerError) {
454
+ committed = error.committed;
455
+ stage =
456
+ error.stage === "commit"
457
+ ? "commit"
458
+ : error.stage === "activate"
459
+ ? "activate"
460
+ : "prepare";
461
+ }
462
+ await this.append({
463
+ type: "context.revision.failed",
464
+ sessionId: this.sessionId,
465
+ data: {
466
+ strategy: "skills_update",
467
+ reason: input.reason,
468
+ stage,
469
+ errorCode: boundedContextErrorCode(
470
+ error instanceof ContextManagerError
471
+ ? error.code
472
+ : error instanceof SessionError
473
+ ? error.code
474
+ : error instanceof Error
475
+ ? error.name
476
+ : "SKILLS_UPDATE_VALIDATION_FAILED",
477
+ ),
478
+ error: `Agent Skills update failed at ${stage}.`,
479
+ committed,
480
+ },
481
+ }).catch(() => undefined);
482
+ throw error;
483
+ }
484
+ }
485
+
486
+ async settleClosedTurnSkills(): Promise<void> {
487
+ const unresolved = this.store.loadSkillActivations(["pending", "dispatched"]);
488
+ if (unresolved.length === 0) {
489
+ return;
490
+ }
491
+ const summary = await this.commitSkillSettlements({
492
+ reason: "activation",
493
+ unresolved,
494
+ });
495
+ await this.append({
496
+ type: "skills.updated",
497
+ sessionId: this.sessionId,
498
+ data: {
499
+ reason: "activation",
500
+ activated: summary.activated,
501
+ refreshed: summary.refreshed,
502
+ deactivated: summary.deactivated,
503
+ unavailable: summary.unavailable,
504
+ revisionNumber: summary.revisionNumber,
505
+ },
506
+ });
507
+ }
508
+
509
+ markModelDispatch(input: {
510
+ iteration: IterationIdentity;
511
+ built: BuiltContextRequest;
512
+ }): void {
513
+ const pending = this.store.loadSkillActivations(["pending"]);
514
+ if (pending.length === 0) {
515
+ return;
516
+ }
517
+ const visibleCanonicalMessageIds = new Set(
518
+ input.built.compiled.entries
519
+ .filter(
520
+ (entry) =>
521
+ entry.representation === "canonical" && entry.message.role === "tool",
522
+ )
523
+ .map((entry) => entry.messageId),
524
+ );
525
+ const included = pending.filter((activation) =>
526
+ visibleCanonicalMessageIds.has(activation.activationMessageId),
527
+ );
528
+ if (included.length === 0) {
529
+ return;
530
+ }
531
+ const dispatched = this.store.markSkillActivationsDispatched({
532
+ iterationId: input.iteration.iterationId,
533
+ activationMessageIds: included.map(
534
+ (activation) => activation.activationMessageId,
535
+ ),
536
+ });
537
+ this.skillCoordinator.markDispatched(
538
+ dispatched.map((activation) => activation.name),
539
+ );
540
+ }
541
+ }
542
+ function compareText(left: string, right: string): number {
543
+ return left < right ? -1 : left > right ? 1 : 0;
544
+ }
@@ -34,13 +34,14 @@ Use WebSearch, when it is available, to look up current information on the web s
34
34
  Use WebFetch to read the content of a specific URL, such as documentation pages found via WebSearch.
35
35
  Prefer Read for reading files instead of using cat on large files.
36
36
  Prefer Write or Edit for changing files instead of shell redirection.
37
- Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
37
+ Use run_in_background=true for persistent processes such as dev servers and watch commands, or when you have independent work to do while a command runs.
38
+ For finite commands whose result is needed next, such as builds, tests, and checks, prefer foreground execution when no independent work remains. Set a sufficient foreground timeout; the call returns as soon as the command finishes.
38
39
  Do not add & to Bash commands; background execution is handled by the Bash tool.
39
40
  Use Bash with tty=true for REPLs, debuggers, interactive prompts, and terminal applications that require a controlling terminal.
40
- Use TaskList to list background shell tasks in the current session.
41
- Use TaskOutput to inspect a task's current status, latest output, or current terminal screen.
42
- Use TaskInput with the returned task ID to send characters to a PTY task. TaskInput does not append Enter; include \\n explicitly, use \\u0003 for Ctrl-C, and use chars="" to wait without writing.
43
- Use TaskStop to stop a background task that is no longer needed.
41
+ TaskList lists background shell tasks in the current session.
42
+ TaskOutput reports a task's current status, latest output, or current terminal screen. For non-PTY logs, offset (1-based) and limit select consecutive lines instead of the default head/tail preview; PTY tasks ignore them. Range truncated=true means byte limits shortened requested content, not that lines outside the range exist. The last observed line of a running log may still be growing; rereading it when polling captures further changes to that line.
43
+ TaskInput sends characters to a PTY task identified by the returned task ID. TaskInput does not append Enter; an explicit \\n sends Enter, \\u0003 sends Ctrl-C, and chars="" waits without writing.
44
+ TaskStop stops a background task that is no longer needed.
44
45
  Do not use ad-hoc kill commands to manage tasks created by Bash.
45
46
  Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
46
47
  Do not send passwords, tokens, or other secrets through TaskInput because tool arguments are stored in session history.
@@ -1,122 +1,16 @@
1
- import { stableJsonStringify, sha256 } from "../model/model-request-preflight";
2
- import { RECALL_TOOL_DEFINITIONS } from "../tools/recall";
3
- import type { ToolDefinition } from "../tools/types";
4
- import type { StoredContextSurfaceV8 } from "./context-surface";
5
- import {
6
- CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
7
- renderRecallRetirementContract,
8
- } from "./recall-retirement-contract";
9
-
10
- export const I4_ACTIVE_RECALL_QUALIFICATION = Object.freeze({
11
- qualificationId: "deepseek-v4-flash-floor-v1",
12
- evaluatedProfile: "deepseek-v4-flash",
13
- manifestVersion: "active-recall-manifest-v1",
14
- manifestSha256: "093679e221e02b71ba5acf54693faa7a299d05dd25f6faabbeb84645d4db4d2d",
15
- graderVersion: "active-recall-deterministic-grader-v1",
16
- fixtureVersion: "active-recall-long-session-fixture-v1",
17
- policyVersion: "active-recall-qualification-policy-v1",
18
- policySha256: "77ca611594d4e9b7b5a597a3a33e35fcaaffae284dc2ac9953ce9a63cce1c009",
19
- positiveReportSha256:
20
- "e827e5e94171328bb2dd7fcaeff91881f04bdfc78361a45e2548da323229b02a",
21
- negativeReportSha256:
22
- "ed379843aee0f193f398a4dff9a18ed338f0edab2cbaf9b628d0dfc402d925a4",
23
- resolvedModel: "deepseek-v4-flash",
24
- recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
25
- recallContractSha256:
26
- "3b6d1a452efea1db5920eb13542571b038667ef4f635551bea375bda6562a39f",
27
- recallToolDefinitionSha256:
28
- "e63ada7cdf9591d1e933cf5e190ea30586e02bbf73aeb5e648db449d75aae009",
29
- metrics: Object.freeze({
30
- fullHistoryTaskSuccessRate: 0.9667,
31
- swapOnlyTaskSuccessRate: 0.9667,
32
- recallOnlyTaskSuccessRate: 1,
33
- recallOnlyActiveRecallRate: 1,
34
- recallOnlySearchGetSuccessRate: 0.3333,
35
- minimumCounterfactualGroupTaskSuccessRate: 1,
36
- invalidRecallCallsPerRecallOnlyTrial: 0,
37
- negativeUnnecessaryRecallRate: 0,
38
- recallOnlyTokenRatioToFullHistory: 1.3739,
39
- recallOnlyLatencyRatioToFullHistory: 1.207,
40
- }),
41
- passed: true,
42
- } as const);
43
-
44
- export const I4_SWAP_ONLY_QUALIFICATION_ID = "swap-only-engineering-v1";
45
-
46
- export type ActiveRecallQualificationEvidence = {
47
- readonly qualificationId: string;
48
- readonly recallContractVersion: string;
49
- readonly recallContractSha256: string;
50
- readonly recallToolDefinitionSha256: string;
51
- readonly passed: boolean;
52
- };
53
-
54
- export type ContextAutomationDecision = {
55
- readonly automaticSwapOnly: boolean;
1
+ /** Product defaults only. Evaluation results and model identities never select these flags.
2
+ * Planners and session boundaries independently validate each operation before execution.
3
+ */
4
+ export type ContextAutomationPolicy = {
5
+ readonly policyId: string;
6
+ readonly automaticSwap: boolean;
56
7
  readonly automaticPrefixRetirement: boolean;
57
- readonly reason:
58
- | "qualified"
59
- | "swap_only_qualified"
60
- | "qualification_pending"
61
- | "unprofiled_model"
62
- | "recall_contract_mismatch"
63
- | "recall_tool_mismatch";
64
- readonly qualificationId?: string;
65
8
  };
66
9
 
67
- export function selectContextAutomation(
68
- input: {
69
- readonly profileName?: string;
70
- readonly surface: StoredContextSurfaceV8;
71
- },
72
- evidence: ActiveRecallQualificationEvidence = I4_ACTIVE_RECALL_QUALIFICATION,
73
- ): ContextAutomationDecision {
74
- if (input.profileName === undefined) {
75
- return disabled("unprofiled_model");
76
- }
77
- if (
78
- input.surface.recallContractVersion !== evidence.recallContractVersion ||
79
- sha256(renderRecallRetirementContract()) !== evidence.recallContractSha256
80
- ) {
81
- return disabled("recall_contract_mismatch");
82
- }
83
- const recallTools = input.surface.toolDefinitions.filter(
84
- (definition) =>
85
- definition.name === "RecallSearch" || definition.name === "RecallGet",
86
- );
87
- if (
88
- recallTools.length !== 2 ||
89
- toolDefinitionsHash(recallTools) !== evidence.recallToolDefinitionSha256 ||
90
- toolDefinitionsHash(RECALL_TOOL_DEFINITIONS) !== evidence.recallToolDefinitionSha256
91
- ) {
92
- return disabled("recall_tool_mismatch");
93
- }
94
- if (!evidence.passed) {
95
- return Object.freeze({
96
- automaticSwapOnly: true,
97
- automaticPrefixRetirement: false,
98
- reason: "swap_only_qualified",
99
- qualificationId: I4_SWAP_ONLY_QUALIFICATION_ID,
100
- });
101
- }
102
- return Object.freeze({
103
- automaticSwapOnly: true,
10
+ export const DEFAULT_CONTEXT_AUTOMATION_POLICY: ContextAutomationPolicy = Object.freeze(
11
+ {
12
+ policyId: "context-automation-v1",
13
+ automaticSwap: true,
104
14
  automaticPrefixRetirement: true,
105
- reason: "qualified",
106
- qualificationId: evidence.qualificationId,
107
- });
108
- }
109
-
110
- function disabled(
111
- reason: Exclude<ContextAutomationDecision["reason"], "qualified">,
112
- ): ContextAutomationDecision {
113
- return Object.freeze({
114
- automaticSwapOnly: false,
115
- automaticPrefixRetirement: false,
116
- reason,
117
- });
118
- }
119
-
120
- function toolDefinitionsHash(definitions: readonly ToolDefinition[]): string {
121
- return sha256(stableJsonStringify(definitions));
122
- }
15
+ },
16
+ );