@code-yeongyu/senpi 2026.8.26-2 → 2026.8.27
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 +35 -0
- package/README.md +3 -3
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +14 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +67 -29
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/credential-pool/classify.d.ts +42 -0
- package/dist/core/credential-pool/classify.d.ts.map +1 -0
- package/dist/core/credential-pool/classify.js +75 -0
- package/dist/core/credential-pool/classify.js.map +1 -0
- package/dist/core/credential-pool/env-slots.d.ts +22 -0
- package/dist/core/credential-pool/env-slots.d.ts.map +1 -0
- package/dist/core/credential-pool/env-slots.js +37 -0
- package/dist/core/credential-pool/env-slots.js.map +1 -0
- package/dist/core/credential-pool/failover.d.ts +54 -0
- package/dist/core/credential-pool/failover.d.ts.map +1 -0
- package/dist/core/credential-pool/failover.js +106 -0
- package/dist/core/credential-pool/failover.js.map +1 -0
- package/dist/core/credential-pool/rotation-stream.d.ts +43 -0
- package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -0
- package/dist/core/credential-pool/rotation-stream.js +96 -0
- package/dist/core/credential-pool/rotation-stream.js.map +1 -0
- package/dist/core/credential-pool/state-store.d.ts +89 -0
- package/dist/core/credential-pool/state-store.d.ts.map +1 -0
- package/dist/core/credential-pool/state-store.js +166 -0
- package/dist/core/credential-pool/state-store.js.map +1 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
- package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts +8 -1
- package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/continuation.js +15 -0
- package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js +2 -2
- package/dist/core/extensions/builtin/goal/direct-input-lifecycle.js.map +1 -1
- package/dist/core/extensions/builtin/goal/format.d.ts +6 -1
- package/dist/core/extensions/builtin/goal/format.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/format.js +6 -0
- package/dist/core/extensions/builtin/goal/format.js.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +5 -2
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +2 -1
- package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
- package/dist/core/extensions/builtin/goal/persistence.js +2 -0
- package/dist/core/extensions/builtin/goal/persistence.js.map +1 -1
- package/dist/core/extensions/builtin/goal/renderers.d.ts +14 -0
- package/dist/core/extensions/builtin/goal/renderers.d.ts.map +1 -0
- package/dist/core/extensions/builtin/goal/renderers.js +153 -0
- package/dist/core/extensions/builtin/goal/renderers.js.map +1 -0
- package/dist/core/extensions/builtin/goal/store.d.ts +6 -2
- package/dist/core/extensions/builtin/goal/store.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/store.js +8 -2
- package/dist/core/extensions/builtin/goal/store.js.map +1 -1
- package/dist/core/extensions/builtin/goal/tool-registration.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/tool-registration.js +16 -6
- package/dist/core/extensions/builtin/goal/tool-registration.js.map +1 -1
- package/dist/core/extensions/builtin/goal/types.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/goal/types.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/index.js +1 -1
- package/dist/core/extensions/builtin/todotools/index.js.map +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.d.ts.map +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.js +1 -1
- package/dist/core/extensions/builtin/todotools/native-todo-mirror.js.map +1 -1
- package/dist/core/model-config-schema.d.ts +85 -12
- package/dist/core/model-config-schema.d.ts.map +1 -1
- package/dist/core/model-config-schema.js +25 -0
- package/dist/core/model-config-schema.js.map +1 -1
- package/dist/core/model-runtime.d.ts +34 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +98 -5
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +32 -7
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +26 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution-fallback.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution-fallback.js +82 -1
- package/dist/modes/interactive/components/tool-execution-fallback.js.map +1 -1
- package/dist/modes/interactive/interactive-host-runtime.d.ts +29 -0
- package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -0
- package/dist/modes/interactive/interactive-host-runtime.js +244 -0
- package/dist/modes/interactive/interactive-host-runtime.js.map +1 -0
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +7 -1
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/host-ensure.d.ts +41 -0
- package/dist/modes/rpc/host-ensure.d.ts.map +1 -0
- package/dist/modes/rpc/host-ensure.js +245 -0
- package/dist/modes/rpc/host-ensure.js.map +1 -0
- package/dist/modes/rpc/host-lifecycle.d.ts +57 -0
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -0
- package/dist/modes/rpc/host-lifecycle.js +491 -0
- package/dist/modes/rpc/host-lifecycle.js.map +1 -0
- package/dist/modes/rpc/host-watchdog.d.ts +33 -0
- package/dist/modes/rpc/host-watchdog.d.ts.map +1 -0
- package/dist/modes/rpc/host-watchdog.js +142 -0
- package/dist/modes/rpc/host-watchdog.js.map +1 -0
- package/dist/modes/rpc/multi-session-host.d.ts +7 -1
- package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
- package/dist/modes/rpc/multi-session-host.js +206 -32
- package/dist/modes/rpc/multi-session-host.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +25 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +83 -10
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +2 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
- package/dist/modes/rpc/session-command-router.js +12 -2
- package/dist/modes/rpc/session-command-router.js.map +1 -1
- package/dist/modes/rpc/session-event-writer.d.ts +13 -2
- package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
- package/dist/modes/rpc/session-event-writer.js +65 -25
- package/dist/modes/rpc/session-event-writer.js.map +1 -1
- package/docs/rpc.md +53 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +18 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +711 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -10
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +9 -3
- package/node_modules/@code-yeongyu/senpi-codemode/src/tool/eval-tool.ts +6 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js +46 -14
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-tool-pairs.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +11 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +30 -16
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js +75 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/classify.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js +81 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/failover.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts +31 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js +65 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/select.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts +12 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js +37 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/pool/slots.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js +32 -6
- package/node_modules/@earendil-works/pi-ai/dist/auth/resolve.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +6 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +45 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/minimax.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +5 -5
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { createHmac, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import lockfile from "proper-lockfile";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getAgentDir } from "../../config.js";
|
|
7
|
+
import { FILE_STORAGE_LOCK_OPTIONS } from "../lockfile-policy.js";
|
|
8
|
+
export const CREDENTIAL_POOL_STATE_FILENAME = "credential-pool-state.json";
|
|
9
|
+
const HEX_256_BIT = /^[0-9a-f]{64}$/;
|
|
10
|
+
const leaseSchema = z.strictObject({
|
|
11
|
+
id: z.string().min(1).max(128),
|
|
12
|
+
expiresAt: z.number().int().positive(),
|
|
13
|
+
});
|
|
14
|
+
const slotStateSchema = z.strictObject({
|
|
15
|
+
stateVersion: z.number().int().nonnegative(),
|
|
16
|
+
blockedUntil: z.number().int().positive().optional(),
|
|
17
|
+
blockReason: z.enum(["auth_error", "rate_limit", "account_disabled"]).optional(),
|
|
18
|
+
failureCount: z.number().int().nonnegative().optional(),
|
|
19
|
+
lastSuccessAt: z.number().int().positive().optional(),
|
|
20
|
+
credentialRevision: z.string().regex(HEX_256_BIT).optional(),
|
|
21
|
+
lease: leaseSchema.optional(),
|
|
22
|
+
});
|
|
23
|
+
const documentSchema = z.strictObject({
|
|
24
|
+
schemaVersion: z.literal(1),
|
|
25
|
+
installationKey: z.string().regex(HEX_256_BIT),
|
|
26
|
+
providers: z.record(z.string(), z.strictObject({
|
|
27
|
+
lanes: z.record(z.string(), z.strictObject({
|
|
28
|
+
slots: z.record(z.string(), slotStateSchema),
|
|
29
|
+
})),
|
|
30
|
+
})),
|
|
31
|
+
});
|
|
32
|
+
export function credentialPoolStatePath(agentDir = getAgentDir()) {
|
|
33
|
+
return join(agentDir, CREDENTIAL_POOL_STATE_FILENAME);
|
|
34
|
+
}
|
|
35
|
+
const FILE_WRITE_OPTIONS = { encoding: "utf-8", mode: 0o600 };
|
|
36
|
+
function freshDocument() {
|
|
37
|
+
return { schemaVersion: 1, installationKey: randomBytes(32).toString("hex"), providers: {} };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The sidecar holds only health, so an unreadable or invalid document resets to
|
|
41
|
+
* a fresh one instead of failing auth resolution; the worst outcome of a reset
|
|
42
|
+
* is a cleared cooldown, never lost credential material.
|
|
43
|
+
*/
|
|
44
|
+
function parseDocument(content) {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = documentSchema.safeParse(JSON.parse(content));
|
|
47
|
+
return parsed.success ? parsed.data : freshDocument();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return freshDocument();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Deadlines are absolute so a restart never re-enables a cooling credential early. */
|
|
54
|
+
export function slotHealth(state, now) {
|
|
55
|
+
if (!state)
|
|
56
|
+
return "ready";
|
|
57
|
+
// Auth and billing blocks have no expiry; only credential replacement clears them.
|
|
58
|
+
if (state.blockReason === "auth_error" || state.blockReason === "account_disabled")
|
|
59
|
+
return "blocked";
|
|
60
|
+
if (state.blockedUntil !== undefined && state.blockedUntil > now)
|
|
61
|
+
return "blocked";
|
|
62
|
+
if (state.blockedUntil !== undefined && state.lease !== undefined && state.lease.expiresAt > now) {
|
|
63
|
+
return "half_open";
|
|
64
|
+
}
|
|
65
|
+
return "ready";
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* File-locked health sidecar for credential pools. Never stores credential
|
|
69
|
+
* material: env slots are keyed by an installation-local HMAC revision so a
|
|
70
|
+
* rotated env value clears its own stale block without persisting a raw key
|
|
71
|
+
* hash anywhere.
|
|
72
|
+
*/
|
|
73
|
+
export class CredentialSlotRepository {
|
|
74
|
+
constructor(path = credentialPoolStatePath()) {
|
|
75
|
+
this.path = path;
|
|
76
|
+
}
|
|
77
|
+
ensureFile() {
|
|
78
|
+
if (existsSync(this.path))
|
|
79
|
+
return;
|
|
80
|
+
mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
|
|
81
|
+
writeFileSync(this.path, JSON.stringify(freshDocument(), null, 2), FILE_WRITE_OPTIONS);
|
|
82
|
+
}
|
|
83
|
+
async withDocument(fn) {
|
|
84
|
+
this.ensureFile();
|
|
85
|
+
const release = await lockfile.lock(this.path, FILE_STORAGE_LOCK_OPTIONS);
|
|
86
|
+
try {
|
|
87
|
+
const document = parseDocument(readFileSync(this.path, "utf-8"));
|
|
88
|
+
const { result, next } = fn(document);
|
|
89
|
+
if (next)
|
|
90
|
+
writeFileSync(this.path, JSON.stringify(next, null, 2), FILE_WRITE_OPTIONS);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await release();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
async installationKey() {
|
|
98
|
+
return this.withDocument((document) => ({ result: document.installationKey, next: document }));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Derives the persistence key for an env slot's health. HMAC over the
|
|
102
|
+
* installation key means the revision is useless outside this installation
|
|
103
|
+
* and never equals a raw hash of the key material.
|
|
104
|
+
*/
|
|
105
|
+
async envCredentialRevision(envVarName, envValue) {
|
|
106
|
+
const key = await this.installationKey();
|
|
107
|
+
return createHmac("sha256", key).update(`${envVarName}\0${envValue}`).digest("hex");
|
|
108
|
+
}
|
|
109
|
+
async listSlots(providerId, laneId) {
|
|
110
|
+
return this.withDocument((document) => ({
|
|
111
|
+
result: { ...document.providers[providerId]?.lanes[laneId]?.slots },
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Atomic read-modify-write for one slot's health. `stateVersion` increments
|
|
116
|
+
* on every write so concurrent mutators can detect lost updates; returning
|
|
117
|
+
* undefined removes the slot state entirely.
|
|
118
|
+
*/
|
|
119
|
+
async mutateSlotState(providerId, laneId, slotId, fn) {
|
|
120
|
+
return this.withDocument((document) => {
|
|
121
|
+
const provider = document.providers[providerId] ?? { lanes: {} };
|
|
122
|
+
const lane = provider.lanes[laneId] ?? { slots: {} };
|
|
123
|
+
const current = lane.slots[slotId];
|
|
124
|
+
const mutated = fn(current);
|
|
125
|
+
const slots = { ...lane.slots };
|
|
126
|
+
let result;
|
|
127
|
+
if (mutated === undefined) {
|
|
128
|
+
delete slots[slotId];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
result = { ...mutated, stateVersion: (current?.stateVersion ?? 0) + 1 };
|
|
132
|
+
slots[slotId] = result;
|
|
133
|
+
}
|
|
134
|
+
const next = {
|
|
135
|
+
...document,
|
|
136
|
+
providers: {
|
|
137
|
+
...document.providers,
|
|
138
|
+
[providerId]: { lanes: { ...provider.lanes, [laneId]: { slots } } },
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
return { result, next };
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Transitions an expired cooldown atomically to half_open: exactly one caller
|
|
147
|
+
* wins the probe lease; everyone else keeps treating the slot as unavailable
|
|
148
|
+
* until the lease expires or the probe settles the block.
|
|
149
|
+
*/
|
|
150
|
+
export async function acquireHalfOpenLease(repository, providerId, laneId, slotId, options = {}) {
|
|
151
|
+
const now = options.now ?? Date.now();
|
|
152
|
+
const leaseTtlMs = options.leaseTtlMs ?? 30_000;
|
|
153
|
+
let lease;
|
|
154
|
+
await repository.mutateSlotState(providerId, laneId, slotId, (current) => {
|
|
155
|
+
if (!current)
|
|
156
|
+
return current;
|
|
157
|
+
const expired = current.blockedUntil !== undefined && current.blockedUntil <= now;
|
|
158
|
+
const leaseLive = current.lease !== undefined && current.lease.expiresAt > now;
|
|
159
|
+
if (!expired || leaseLive || current.blockReason === "auth_error")
|
|
160
|
+
return current;
|
|
161
|
+
lease = { leaseId: randomUUID(), expiresAt: now + leaseTtlMs };
|
|
162
|
+
return { ...current, lease: { id: lease.leaseId, expiresAt: lease.expiresAt } };
|
|
163
|
+
});
|
|
164
|
+
return lease;
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=state-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store.js","sourceRoot":"","sources":["../../../src/core/credential-pool/state-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHmac, randomBytes, randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport lockfile from \"proper-lockfile\";\nimport { z } from \"zod\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { FILE_STORAGE_LOCK_OPTIONS } from \"../lockfile-policy.ts\";\n\nexport const CREDENTIAL_POOL_STATE_FILENAME = \"credential-pool-state.json\";\n\nconst HEX_256_BIT = /^[0-9a-f]{64}$/;\n\nconst leaseSchema = z.strictObject({\n\tid: z.string().min(1).max(128),\n\texpiresAt: z.number().int().positive(),\n});\n\nconst slotStateSchema = z.strictObject({\n\tstateVersion: z.number().int().nonnegative(),\n\tblockedUntil: z.number().int().positive().optional(),\n\tblockReason: z.enum([\"auth_error\", \"rate_limit\", \"account_disabled\"]).optional(),\n\tfailureCount: z.number().int().nonnegative().optional(),\n\tlastSuccessAt: z.number().int().positive().optional(),\n\tcredentialRevision: z.string().regex(HEX_256_BIT).optional(),\n\tlease: leaseSchema.optional(),\n});\n\nexport type CredentialSlotState = Readonly<z.infer<typeof slotStateSchema>>;\n\nconst documentSchema = z.strictObject({\n\tschemaVersion: z.literal(1),\n\tinstallationKey: z.string().regex(HEX_256_BIT),\n\tproviders: z.record(\n\t\tz.string(),\n\t\tz.strictObject({\n\t\t\tlanes: z.record(\n\t\t\t\tz.string(),\n\t\t\t\tz.strictObject({\n\t\t\t\t\tslots: z.record(z.string(), slotStateSchema),\n\t\t\t\t}),\n\t\t\t),\n\t\t}),\n\t),\n});\n\nexport type CredentialPoolStateDocument = z.infer<typeof documentSchema>;\n\nexport function credentialPoolStatePath(agentDir: string = getAgentDir()): string {\n\treturn join(agentDir, CREDENTIAL_POOL_STATE_FILENAME);\n}\n\nconst FILE_WRITE_OPTIONS = { encoding: \"utf-8\", mode: 0o600 } as const;\n\nfunction freshDocument(): CredentialPoolStateDocument {\n\treturn { schemaVersion: 1, installationKey: randomBytes(32).toString(\"hex\"), providers: {} };\n}\n\n/**\n * The sidecar holds only health, so an unreadable or invalid document resets to\n * a fresh one instead of failing auth resolution; the worst outcome of a reset\n * is a cleared cooldown, never lost credential material.\n */\nfunction parseDocument(content: string): CredentialPoolStateDocument {\n\ttry {\n\t\tconst parsed = documentSchema.safeParse(JSON.parse(content));\n\t\treturn parsed.success ? parsed.data : freshDocument();\n\t} catch {\n\t\treturn freshDocument();\n\t}\n}\n\nexport type SlotHealth = \"ready\" | \"blocked\" | \"half_open\";\n\n/** Deadlines are absolute so a restart never re-enables a cooling credential early. */\nexport function slotHealth(state: CredentialSlotState | undefined, now: number): SlotHealth {\n\tif (!state) return \"ready\";\n\t// Auth and billing blocks have no expiry; only credential replacement clears them.\n\tif (state.blockReason === \"auth_error\" || state.blockReason === \"account_disabled\") return \"blocked\";\n\tif (state.blockedUntil !== undefined && state.blockedUntil > now) return \"blocked\";\n\tif (state.blockedUntil !== undefined && state.lease !== undefined && state.lease.expiresAt > now) {\n\t\treturn \"half_open\";\n\t}\n\treturn \"ready\";\n}\n\n/**\n * File-locked health sidecar for credential pools. Never stores credential\n * material: env slots are keyed by an installation-local HMAC revision so a\n * rotated env value clears its own stale block without persisting a raw key\n * hash anywhere.\n */\nexport class CredentialSlotRepository {\n\tprivate readonly path: string;\n\n\tconstructor(path: string = credentialPoolStatePath()) {\n\t\tthis.path = path;\n\t}\n\n\tprivate ensureFile(): void {\n\t\tif (existsSync(this.path)) return;\n\t\tmkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });\n\t\twriteFileSync(this.path, JSON.stringify(freshDocument(), null, 2), FILE_WRITE_OPTIONS);\n\t}\n\n\tprivate async withDocument<T>(\n\t\tfn: (document: CredentialPoolStateDocument) => { result: T; next?: CredentialPoolStateDocument },\n\t): Promise<T> {\n\t\tthis.ensureFile();\n\t\tconst release = await lockfile.lock(this.path, FILE_STORAGE_LOCK_OPTIONS);\n\t\ttry {\n\t\t\tconst document = parseDocument(readFileSync(this.path, \"utf-8\"));\n\t\t\tconst { result, next } = fn(document);\n\t\t\tif (next) writeFileSync(this.path, JSON.stringify(next, null, 2), FILE_WRITE_OPTIONS);\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t}\n\n\tasync installationKey(): Promise<string> {\n\t\treturn this.withDocument((document) => ({ result: document.installationKey, next: document }));\n\t}\n\n\t/**\n\t * Derives the persistence key for an env slot's health. HMAC over the\n\t * installation key means the revision is useless outside this installation\n\t * and never equals a raw hash of the key material.\n\t */\n\tasync envCredentialRevision(envVarName: string, envValue: string): Promise<string> {\n\t\tconst key = await this.installationKey();\n\t\treturn createHmac(\"sha256\", key).update(`${envVarName}\\0${envValue}`).digest(\"hex\");\n\t}\n\n\tasync listSlots(providerId: string, laneId: string): Promise<Record<string, CredentialSlotState>> {\n\t\treturn this.withDocument((document) => ({\n\t\t\tresult: { ...document.providers[providerId]?.lanes[laneId]?.slots },\n\t\t}));\n\t}\n\n\t/**\n\t * Atomic read-modify-write for one slot's health. `stateVersion` increments\n\t * on every write so concurrent mutators can detect lost updates; returning\n\t * undefined removes the slot state entirely.\n\t */\n\tasync mutateSlotState(\n\t\tproviderId: string,\n\t\tlaneId: string,\n\t\tslotId: string,\n\t\tfn: (current: CredentialSlotState | undefined) => Omit<CredentialSlotState, \"stateVersion\"> | undefined,\n\t): Promise<CredentialSlotState | undefined> {\n\t\treturn this.withDocument((document) => {\n\t\t\tconst provider = document.providers[providerId] ?? { lanes: {} };\n\t\t\tconst lane = provider.lanes[laneId] ?? { slots: {} };\n\t\t\tconst current = lane.slots[slotId];\n\t\t\tconst mutated = fn(current);\n\t\t\tconst slots = { ...lane.slots };\n\t\t\tlet result: CredentialSlotState | undefined;\n\t\t\tif (mutated === undefined) {\n\t\t\t\tdelete slots[slotId];\n\t\t\t} else {\n\t\t\t\tresult = { ...mutated, stateVersion: (current?.stateVersion ?? 0) + 1 };\n\t\t\t\tslots[slotId] = result;\n\t\t\t}\n\t\t\tconst next: CredentialPoolStateDocument = {\n\t\t\t\t...document,\n\t\t\t\tproviders: {\n\t\t\t\t\t...document.providers,\n\t\t\t\t\t[providerId]: { lanes: { ...provider.lanes, [laneId]: { slots } } },\n\t\t\t\t},\n\t\t\t};\n\t\t\treturn { result, next };\n\t\t});\n\t}\n}\n\nexport type HalfOpenLease = { leaseId: string; expiresAt: number };\n\n/**\n * Transitions an expired cooldown atomically to half_open: exactly one caller\n * wins the probe lease; everyone else keeps treating the slot as unavailable\n * until the lease expires or the probe settles the block.\n */\nexport async function acquireHalfOpenLease(\n\trepository: CredentialSlotRepository,\n\tproviderId: string,\n\tlaneId: string,\n\tslotId: string,\n\toptions: { now?: number; leaseTtlMs?: number } = {},\n): Promise<HalfOpenLease | undefined> {\n\tconst now = options.now ?? Date.now();\n\tconst leaseTtlMs = options.leaseTtlMs ?? 30_000;\n\tlet lease: HalfOpenLease | undefined;\n\tawait repository.mutateSlotState(providerId, laneId, slotId, (current) => {\n\t\tif (!current) return current;\n\t\tconst expired = current.blockedUntil !== undefined && current.blockedUntil <= now;\n\t\tconst leaseLive = current.lease !== undefined && current.lease.expiresAt > now;\n\t\tif (!expired || leaseLive || current.blockReason === \"auth_error\") return current;\n\t\tlease = { leaseId: randomUUID(), expiresAt: now + leaseTtlMs };\n\t\treturn { ...current, lease: { id: lease.leaseId, expiresAt: lease.expiresAt } };\n\t});\n\treturn lease;\n}\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const CONTINUATION_CAP_BLOCKED_REASON = "continuation cap reached";
|
|
2
2
|
export declare const REPETITION_BLOCKED_REASON = "repeated assistant output";
|
|
3
3
|
export declare const LENGTH_EXHAUSTED_BLOCKED_REASON = "output truncation repeated";
|
|
4
|
+
export declare const UNATTENDED_CONTINUATION_BLOCKED_REASON = "unattended continuation limit reached";
|
|
4
5
|
export declare const PROVIDER_ERROR_BLOCKED_REASON = "provider error ended the turn (retries exhausted)";
|
|
5
6
|
export declare function isMechanicalContinuationBlock(blockedReason: string | undefined): boolean;
|
|
6
7
|
export declare function continuationCapRecoveryHint(blockedReason: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"continuation-recovery.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation-recovery.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,6BAA6B,CAAC;AAC1E,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAC5E,eAAO,MAAM,6BAA6B,sDAAsD,CAAC;
|
|
1
|
+
{"version":3,"file":"continuation-recovery.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation-recovery.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,6BAA6B,CAAC;AAC1E,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAC5E,eAAO,MAAM,sCAAsC,0CAA0C,CAAC;AAC9F,eAAO,MAAM,6BAA6B,sDAAsD,CAAC;AAiBjG,wBAAgB,6BAA6B,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGxF;AAED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAGzE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const CONTINUATION_CAP_BLOCKED_REASON = "continuation cap reached";
|
|
2
2
|
export const REPETITION_BLOCKED_REASON = "repeated assistant output";
|
|
3
3
|
export const LENGTH_EXHAUSTED_BLOCKED_REASON = "output truncation repeated";
|
|
4
|
+
export const UNATTENDED_CONTINUATION_BLOCKED_REASON = "unattended continuation limit reached";
|
|
4
5
|
export const PROVIDER_ERROR_BLOCKED_REASON = "provider error ended the turn (retries exhausted)";
|
|
5
6
|
// Mechanical blocks are stops the runtime imposed on itself, not decisions the
|
|
6
7
|
// user or the model made. A terminal provider error belongs here: the provider
|
|
@@ -11,6 +12,7 @@ const MECHANICAL_CONTINUATION_BLOCKS = [
|
|
|
11
12
|
CONTINUATION_CAP_BLOCKED_REASON,
|
|
12
13
|
REPETITION_BLOCKED_REASON,
|
|
13
14
|
LENGTH_EXHAUSTED_BLOCKED_REASON,
|
|
15
|
+
UNATTENDED_CONTINUATION_BLOCKED_REASON,
|
|
14
16
|
PROVIDER_ERROR_BLOCKED_REASON,
|
|
15
17
|
];
|
|
16
18
|
const RESUME_GUIDANCE = "Send any message to resume.";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"continuation-recovery.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation-recovery.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAAG,0BAA0B,CAAC;AAC1E,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAC;AACrE,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAC;AAC5E,MAAM,CAAC,MAAM,6BAA6B,GAAG,mDAAmD,CAAC;AAEjG,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,4EAA4E;AAC5E,8BAA8B;AAC9B,MAAM,8BAA8B,GAAsB;IACzD,+BAA+B;IAC/B,yBAAyB;IACzB,+BAA+B;IAC/B,6BAA6B;CAC7B,CAAC;AAEF,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAEtD,MAAM,UAAU,6BAA6B,CAAC,aAAiC;IAC9E,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,8BAA8B,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,aAAqB;IAChE,IAAI,CAAC,6BAA6B,CAAC,aAAa,CAAC;QAAE,OAAO,8BAA8B,aAAa,EAAE,CAAC;IACxG,OAAO,8BAA8B,aAAa,KAAK,eAAe,EAAE,CAAC;AAC1E,CAAC","sourcesContent":["export const CONTINUATION_CAP_BLOCKED_REASON = \"continuation cap reached\";\nexport const REPETITION_BLOCKED_REASON = \"repeated assistant output\";\nexport const LENGTH_EXHAUSTED_BLOCKED_REASON = \"output truncation repeated\";\nexport const PROVIDER_ERROR_BLOCKED_REASON = \"provider error ended the turn (retries exhausted)\";\n\n// Mechanical blocks are stops the runtime imposed on itself, not decisions the\n// user or the model made. A terminal provider error belongs here: the provider\n// failing is infrastructure, and the user's next message is exactly the retry\n// signal, so the goal resumes instead of stranding behind a block that only\n// `/goal resume` could clear.\nconst MECHANICAL_CONTINUATION_BLOCKS: readonly string[] = [\n\tCONTINUATION_CAP_BLOCKED_REASON,\n\tREPETITION_BLOCKED_REASON,\n\tLENGTH_EXHAUSTED_BLOCKED_REASON,\n\tPROVIDER_ERROR_BLOCKED_REASON,\n];\n\nconst RESUME_GUIDANCE = \"Send any message to resume.\";\n\nexport function isMechanicalContinuationBlock(blockedReason: string | undefined): boolean {\n\tif (blockedReason === undefined) return false;\n\treturn MECHANICAL_CONTINUATION_BLOCKS.includes(blockedReason);\n}\n\nexport function continuationCapRecoveryHint(blockedReason: string): string {\n\tif (!isMechanicalContinuationBlock(blockedReason)) return `Goal continuation blocked: ${blockedReason}`;\n\treturn `Goal continuation blocked: ${blockedReason}. ${RESUME_GUIDANCE}`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"continuation-recovery.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation-recovery.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAAG,0BAA0B,CAAC;AAC1E,MAAM,CAAC,MAAM,yBAAyB,GAAG,2BAA2B,CAAC;AACrE,MAAM,CAAC,MAAM,+BAA+B,GAAG,4BAA4B,CAAC;AAC5E,MAAM,CAAC,MAAM,sCAAsC,GAAG,uCAAuC,CAAC;AAC9F,MAAM,CAAC,MAAM,6BAA6B,GAAG,mDAAmD,CAAC;AAEjG,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,4EAA4E;AAC5E,8BAA8B;AAC9B,MAAM,8BAA8B,GAAsB;IACzD,+BAA+B;IAC/B,yBAAyB;IACzB,+BAA+B;IAC/B,sCAAsC;IACtC,6BAA6B;CAC7B,CAAC;AAEF,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAEtD,MAAM,UAAU,6BAA6B,CAAC,aAAiC;IAC9E,IAAI,aAAa,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,8BAA8B,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,aAAqB;IAChE,IAAI,CAAC,6BAA6B,CAAC,aAAa,CAAC;QAAE,OAAO,8BAA8B,aAAa,EAAE,CAAC;IACxG,OAAO,8BAA8B,aAAa,KAAK,eAAe,EAAE,CAAC;AAC1E,CAAC","sourcesContent":["export const CONTINUATION_CAP_BLOCKED_REASON = \"continuation cap reached\";\nexport const REPETITION_BLOCKED_REASON = \"repeated assistant output\";\nexport const LENGTH_EXHAUSTED_BLOCKED_REASON = \"output truncation repeated\";\nexport const UNATTENDED_CONTINUATION_BLOCKED_REASON = \"unattended continuation limit reached\";\nexport const PROVIDER_ERROR_BLOCKED_REASON = \"provider error ended the turn (retries exhausted)\";\n\n// Mechanical blocks are stops the runtime imposed on itself, not decisions the\n// user or the model made. A terminal provider error belongs here: the provider\n// failing is infrastructure, and the user's next message is exactly the retry\n// signal, so the goal resumes instead of stranding behind a block that only\n// `/goal resume` could clear.\nconst MECHANICAL_CONTINUATION_BLOCKS: readonly string[] = [\n\tCONTINUATION_CAP_BLOCKED_REASON,\n\tREPETITION_BLOCKED_REASON,\n\tLENGTH_EXHAUSTED_BLOCKED_REASON,\n\tUNATTENDED_CONTINUATION_BLOCKED_REASON,\n\tPROVIDER_ERROR_BLOCKED_REASON,\n];\n\nconst RESUME_GUIDANCE = \"Send any message to resume.\";\n\nexport function isMechanicalContinuationBlock(blockedReason: string | undefined): boolean {\n\tif (blockedReason === undefined) return false;\n\treturn MECHANICAL_CONTINUATION_BLOCKS.includes(blockedReason);\n}\n\nexport function continuationCapRecoveryHint(blockedReason: string): string {\n\tif (!isMechanicalContinuationBlock(blockedReason)) return `Goal continuation blocked: ${blockedReason}`;\n\treturn `Goal continuation blocked: ${blockedReason}. ${RESUME_GUIDANCE}`;\n}\n"]}
|
|
@@ -7,6 +7,13 @@ export declare const GOAL_CONTINUATION_CAP = 8;
|
|
|
7
7
|
export declare const GOAL_STALL_TOOLLESS_THRESHOLD = 3;
|
|
8
8
|
export declare const GOAL_REPETITION_HASH_STREAK = 3;
|
|
9
9
|
export declare const GOAL_LENGTH_RECOVERY_LIMIT = 1;
|
|
10
|
+
/**
|
|
11
|
+
* Hard budget of automatic continuations without accepted direct user input (#1139).
|
|
12
|
+
* Progress and tool use never refill it: only direct input does. Stays above the
|
|
13
|
+
* #447 distinct-progress pin (50) and an 8-hour monitor-backstop cadence
|
|
14
|
+
* (~120 deliveries at 240s), below the observed 289-continuation incident run.
|
|
15
|
+
*/
|
|
16
|
+
export declare const GOAL_UNATTENDED_CONTINUATION_LIMIT = 150;
|
|
10
17
|
export declare const GOAL_USER_GRACE_DELAY_MS = 10000;
|
|
11
18
|
export type GoalContinuationPath = "immediate" | "monitorDelayed" | "userGrace" | "sessionStart" | "systemRecovery" | "providerRecovery";
|
|
12
19
|
export type GoalContinuationInput = {
|
|
@@ -29,7 +36,7 @@ export type GoalContinuationVerdict = {
|
|
|
29
36
|
stallNotice: boolean;
|
|
30
37
|
} | {
|
|
31
38
|
kind: "deny";
|
|
32
|
-
reason: "not-eligible" | "single-flight" | "cap" | "stale" | "repetition" | "length-exhausted";
|
|
39
|
+
reason: "not-eligible" | "single-flight" | "cap" | "stale" | "repetition" | "length-exhausted" | "unattended";
|
|
33
40
|
};
|
|
34
41
|
export declare function shouldQueueGoalContinuationWhenIdle(goal: Goal | null, isIdle: boolean, hasPendingMessages: boolean): goal is Goal;
|
|
35
42
|
export declare function shouldQueueGoalContinuationAfterAgentEnd(goal: Goal | null, hasPendingMessages: boolean, messages: readonly AgentMessage[]): goal is Goal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"continuation.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,KAAK,qBAAqB,GAAG,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AAG1E,eAAO,MAAM,qBAAqB,IAAI,CAAC;AACvC,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAC7C,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAC5C,eAAO,MAAM,wBAAwB,QAAS,CAAC;AAE/C,MAAM,MAAM,oBAAoB,GAC7B,WAAW,GACX,gBAAgB,GAChB,WAAW,GACX,cAAc,GACd,gBAAgB,GAChB,kBAAkB,CAAC;AAEtB,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC;IACzE,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,yBAAyB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9C,QAAQ,CAAC,2BAA2B,EAAE,MAAM,CAAC;IAC7C,QAAQ,CAAC,4BAA4B,EAAE,SAAS,MAAM,EAAE,CAAC;IACzD,QAAQ,CAAC,0BAA0B,EAAE,MAAM,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,GACtE;IACA,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,cAAc,GAAG,eAAe,GAAG,KAAK,GAAG,OAAO,GAAG,YAAY,GAAG,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"continuation.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,13 @@ export const GOAL_CONTINUATION_CAP = 8;
|
|
|
2
2
|
export const GOAL_STALL_TOOLLESS_THRESHOLD = 3;
|
|
3
3
|
export const GOAL_REPETITION_HASH_STREAK = 3;
|
|
4
4
|
export const GOAL_LENGTH_RECOVERY_LIMIT = 1;
|
|
5
|
+
/**
|
|
6
|
+
* Hard budget of automatic continuations without accepted direct user input (#1139).
|
|
7
|
+
* Progress and tool use never refill it: only direct input does. Stays above the
|
|
8
|
+
* #447 distinct-progress pin (50) and an 8-hour monitor-backstop cadence
|
|
9
|
+
* (~120 deliveries at 240s), below the observed 289-continuation incident run.
|
|
10
|
+
*/
|
|
11
|
+
export const GOAL_UNATTENDED_CONTINUATION_LIMIT = 150;
|
|
5
12
|
export const GOAL_USER_GRACE_DELAY_MS = 10_000;
|
|
6
13
|
export function shouldQueueGoalContinuationWhenIdle(goal, isIdle, hasPendingMessages) {
|
|
7
14
|
return goal?.status === "active" && isIdle && !hasPendingMessages;
|
|
@@ -47,6 +54,8 @@ export function evaluateGoalContinuation(input) {
|
|
|
47
54
|
return { kind: "deny", reason: "single-flight" };
|
|
48
55
|
if (hasRepeatedNormalizedOutputHash(input.recentNormalizedOutputHashes))
|
|
49
56
|
return { kind: "deny", reason: "repetition" };
|
|
57
|
+
if (isUnattendedBudgetExhausted(input))
|
|
58
|
+
return { kind: "deny", reason: "unattended" };
|
|
50
59
|
if (input.consecutiveContinuations >= GOAL_CONTINUATION_CAP) {
|
|
51
60
|
return { kind: "deny", reason: "cap" };
|
|
52
61
|
}
|
|
@@ -106,6 +115,12 @@ function isEligibleForGoalContinuation(input) {
|
|
|
106
115
|
}
|
|
107
116
|
return input.isIdle;
|
|
108
117
|
}
|
|
118
|
+
/** Monitor-delayed deliveries are exempt: armed-wake waiting is by-design and rate-limited by the cache-aware timer. */
|
|
119
|
+
function isUnattendedBudgetExhausted(input) {
|
|
120
|
+
if (input.path === "monitorDelayed")
|
|
121
|
+
return false;
|
|
122
|
+
return (input.goal?.unattendedContinuations ?? 0) >= GOAL_UNATTENDED_CONTINUATION_LIMIT;
|
|
123
|
+
}
|
|
109
124
|
function hasRepeatedNormalizedOutputHash(hashes) {
|
|
110
125
|
const latestHash = hashes.at(-1);
|
|
111
126
|
if (latestHash === undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"continuation.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { Goal } from \"./types.ts\";\n\ntype AssistantAgentMessage = Extract<AgentMessage, { role: \"assistant\" }>;\ntype ToolResultAgentMessage = Extract<AgentMessage, { role: \"toolResult\" }>;\n\nexport const GOAL_CONTINUATION_CAP = 8;\nexport const GOAL_STALL_TOOLLESS_THRESHOLD = 3;\nexport const GOAL_REPETITION_HASH_STREAK = 3;\nexport const GOAL_LENGTH_RECOVERY_LIMIT = 1;\nexport const GOAL_USER_GRACE_DELAY_MS = 10_000;\n\nexport type GoalContinuationPath =\n\t| \"immediate\"\n\t| \"monitorDelayed\"\n\t| \"userGrace\"\n\t| \"sessionStart\"\n\t| \"systemRecovery\"\n\t| \"providerRecovery\";\n\nexport type GoalContinuationInput = {\n\treadonly goal: Goal | null;\n\treadonly isIdle: boolean;\n\treadonly hasPendingMessages: boolean;\n\treadonly path: GoalContinuationPath;\n\treadonly lastStopReason: AssistantAgentMessage[\"stopReason\"] | undefined;\n\treadonly consecutiveContinuations: number;\n\treadonly lastContinuationSignature: string | undefined;\n\treadonly currentSignature: string | undefined;\n\treadonly consecutiveLengthRecoveries: number;\n\treadonly recentNormalizedOutputHashes: readonly string[];\n\treadonly toollessContinuationStreak: number;\n\treadonly continuationPending: boolean;\n};\n\nexport type GoalContinuationVerdict =\n\t| { kind: \"continue\"; prompt: \"full\" | \"minimal\"; stallNotice: boolean }\n\t| {\n\t\t\tkind: \"deny\";\n\t\t\treason: \"not-eligible\" | \"single-flight\" | \"cap\" | \"stale\" | \"repetition\" | \"length-exhausted\";\n\t };\n\nexport function shouldQueueGoalContinuationWhenIdle(\n\tgoal: Goal | null,\n\tisIdle: boolean,\n\thasPendingMessages: boolean,\n): goal is Goal {\n\treturn goal?.status === \"active\" && isIdle && !hasPendingMessages;\n}\n\nexport function shouldQueueGoalContinuationAfterAgentEnd(\n\tgoal: Goal | null,\n\thasPendingMessages: boolean,\n\tmessages: readonly AgentMessage[],\n): goal is Goal {\n\treturn goal?.status === \"active\" && !hasPendingMessages && didAgentEndCleanly(messages);\n}\n\nfunction didAgentEndCleanly(messages: readonly AgentMessage[]): boolean {\n\tconst lastAssistantIndex = findLastAssistantMessageIndex(messages);\n\tif (lastAssistantIndex === undefined) return false;\n\n\tconst lastAssistant = messages[lastAssistantIndex];\n\tif (lastAssistant?.role !== \"assistant\" || !isContinuableStopReason(lastAssistant.stopReason)) return false;\n\n\tfor (let index = lastAssistantIndex + 1; index < messages.length; index++) {\n\t\tconst message = messages[index];\n\t\tif (message?.role === \"toolResult\" && isAbortedToolResult(message)) return false;\n\t}\n\treturn true;\n}\n\nfunction findLastAssistantMessageIndex(messages: readonly AgentMessage[]): number | undefined {\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message?.role === \"assistant\") {\n\t\t\treturn index;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction isContinuableStopReason(stopReason: AssistantAgentMessage[\"stopReason\"]): boolean {\n\treturn stopReason === \"stop\" || stopReason === \"length\";\n}\n\nfunction isAbortedToolResult(message: ToolResultAgentMessage): boolean {\n\tif (!message.isError) return false;\n\treturn message.content.some((content) => content.type === \"text\" && /\\babort(?:ed)?\\b/i.test(content.text));\n}\n\nexport function evaluateGoalContinuation(input: GoalContinuationInput): GoalContinuationVerdict {\n\tif (!isEligibleForGoalContinuation(input)) return { kind: \"deny\", reason: \"not-eligible\" };\n\tif (input.continuationPending) return { kind: \"deny\", reason: \"single-flight\" };\n\tif (hasRepeatedNormalizedOutputHash(input.recentNormalizedOutputHashes))\n\t\treturn { kind: \"deny\", reason: \"repetition\" };\n\tif (input.consecutiveContinuations >= GOAL_CONTINUATION_CAP) {\n\t\treturn { kind: \"deny\", reason: \"cap\" };\n\t}\n\tif (\n\t\t(input.path === \"immediate\" || input.path === \"userGrace\") &&\n\t\tinput.lastContinuationSignature !== undefined &&\n\t\tinput.lastContinuationSignature === input.currentSignature\n\t) {\n\t\treturn { kind: \"deny\", reason: \"stale\" };\n\t}\n\tif (input.lastStopReason === \"length\") {\n\t\tif (input.consecutiveLengthRecoveries >= GOAL_LENGTH_RECOVERY_LIMIT) {\n\t\t\treturn { kind: \"deny\", reason: \"length-exhausted\" };\n\t\t}\n\t\treturn { kind: \"continue\", prompt: \"minimal\", stallNotice: shouldShowStallNotice(input) };\n\t}\n\treturn { kind: \"continue\", prompt: \"full\", stallNotice: shouldShowStallNotice(input) };\n}\n\n/** Produces a stable, whitespace-insensitive representation for repetition detection. */\nexport function normalizeAssistantText(text: string): string {\n\treturn text.toLowerCase().replace(/\\s+/g, \" \").trim();\n}\n\n/** Hashes normalized assistant text with the deterministic 32-bit FNV-1a algorithm. */\nexport function hashAssistantText(text: string): string {\n\tconst normalized = normalizeAssistantText(text);\n\tlet hash = 0x811c9dc5;\n\tfor (let index = 0; index < normalized.length; index++) {\n\t\thash ^= normalized.charCodeAt(index);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/**\n * Identifies the observable goal progress for stale-continuation admission.\n * Deliberately excludes goal.updatedAt because usage accounting updates it after every agent turn.\n */\nexport function buildGoalContinuationSignature(\n\tgoal: Pick<Goal, \"id\">,\n\topenTodos: number,\n\ttotalTodos: number,\n\tlastAssistantTextHash: string,\n): string {\n\treturn `${goal.id}:${openTodos}/${totalTodos}:${lastAssistantTextHash}`;\n}\n\nexport function hasGoalContinuationProgress(\n\tinput: Pick<GoalContinuationInput, \"lastContinuationSignature\" | \"currentSignature\">,\n): boolean {\n\treturn (\n\t\tinput.lastContinuationSignature !== undefined &&\n\t\tinput.currentSignature !== undefined &&\n\t\tinput.lastContinuationSignature !== input.currentSignature\n\t);\n}\n\nexport function continuationTurnUsedTools(messages: readonly AgentMessage[]): boolean {\n\treturn messages.some((message) => {\n\t\tif (message?.role === \"toolResult\") return true;\n\t\treturn message?.role === \"assistant\" && message.content.some((content) => content.type === \"toolCall\");\n\t});\n}\n\nfunction isEligibleForGoalContinuation(input: GoalContinuationInput): boolean {\n\tif (input.goal?.status !== \"active\" || input.hasPendingMessages) return false;\n\tif (input.path === \"systemRecovery\" || input.path === \"providerRecovery\") return true;\n\tif (input.path === \"immediate\") {\n\t\treturn input.lastStopReason !== undefined && isContinuableStopReason(input.lastStopReason);\n\t}\n\treturn input.isIdle;\n}\n\nfunction hasRepeatedNormalizedOutputHash(hashes: readonly string[]): boolean {\n\tconst latestHash = hashes.at(-1);\n\tif (latestHash === undefined) return false;\n\n\tlet streak = 0;\n\tfor (let index = hashes.length - 1; index >= 0 && hashes[index] === latestHash; index--) {\n\t\tstreak += 1;\n\t}\n\treturn streak >= GOAL_REPETITION_HASH_STREAK;\n}\n\nfunction shouldShowStallNotice(input: GoalContinuationInput): boolean {\n\treturn input.toollessContinuationStreak >= GOAL_STALL_TOOLLESS_THRESHOLD;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"continuation.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/continuation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { Goal } from \"./types.ts\";\n\ntype AssistantAgentMessage = Extract<AgentMessage, { role: \"assistant\" }>;\ntype ToolResultAgentMessage = Extract<AgentMessage, { role: \"toolResult\" }>;\n\nexport const GOAL_CONTINUATION_CAP = 8;\nexport const GOAL_STALL_TOOLLESS_THRESHOLD = 3;\nexport const GOAL_REPETITION_HASH_STREAK = 3;\nexport const GOAL_LENGTH_RECOVERY_LIMIT = 1;\n/**\n * Hard budget of automatic continuations without accepted direct user input (#1139).\n * Progress and tool use never refill it: only direct input does. Stays above the\n * #447 distinct-progress pin (50) and an 8-hour monitor-backstop cadence\n * (~120 deliveries at 240s), below the observed 289-continuation incident run.\n */\nexport const GOAL_UNATTENDED_CONTINUATION_LIMIT = 150;\nexport const GOAL_USER_GRACE_DELAY_MS = 10_000;\n\nexport type GoalContinuationPath =\n\t| \"immediate\"\n\t| \"monitorDelayed\"\n\t| \"userGrace\"\n\t| \"sessionStart\"\n\t| \"systemRecovery\"\n\t| \"providerRecovery\";\n\nexport type GoalContinuationInput = {\n\treadonly goal: Goal | null;\n\treadonly isIdle: boolean;\n\treadonly hasPendingMessages: boolean;\n\treadonly path: GoalContinuationPath;\n\treadonly lastStopReason: AssistantAgentMessage[\"stopReason\"] | undefined;\n\treadonly consecutiveContinuations: number;\n\treadonly lastContinuationSignature: string | undefined;\n\treadonly currentSignature: string | undefined;\n\treadonly consecutiveLengthRecoveries: number;\n\treadonly recentNormalizedOutputHashes: readonly string[];\n\treadonly toollessContinuationStreak: number;\n\treadonly continuationPending: boolean;\n};\n\nexport type GoalContinuationVerdict =\n\t| { kind: \"continue\"; prompt: \"full\" | \"minimal\"; stallNotice: boolean }\n\t| {\n\t\t\tkind: \"deny\";\n\t\t\treason: \"not-eligible\" | \"single-flight\" | \"cap\" | \"stale\" | \"repetition\" | \"length-exhausted\" | \"unattended\";\n\t };\n\nexport function shouldQueueGoalContinuationWhenIdle(\n\tgoal: Goal | null,\n\tisIdle: boolean,\n\thasPendingMessages: boolean,\n): goal is Goal {\n\treturn goal?.status === \"active\" && isIdle && !hasPendingMessages;\n}\n\nexport function shouldQueueGoalContinuationAfterAgentEnd(\n\tgoal: Goal | null,\n\thasPendingMessages: boolean,\n\tmessages: readonly AgentMessage[],\n): goal is Goal {\n\treturn goal?.status === \"active\" && !hasPendingMessages && didAgentEndCleanly(messages);\n}\n\nfunction didAgentEndCleanly(messages: readonly AgentMessage[]): boolean {\n\tconst lastAssistantIndex = findLastAssistantMessageIndex(messages);\n\tif (lastAssistantIndex === undefined) return false;\n\n\tconst lastAssistant = messages[lastAssistantIndex];\n\tif (lastAssistant?.role !== \"assistant\" || !isContinuableStopReason(lastAssistant.stopReason)) return false;\n\n\tfor (let index = lastAssistantIndex + 1; index < messages.length; index++) {\n\t\tconst message = messages[index];\n\t\tif (message?.role === \"toolResult\" && isAbortedToolResult(message)) return false;\n\t}\n\treturn true;\n}\n\nfunction findLastAssistantMessageIndex(messages: readonly AgentMessage[]): number | undefined {\n\tfor (let index = messages.length - 1; index >= 0; index--) {\n\t\tconst message = messages[index];\n\t\tif (message?.role === \"assistant\") {\n\t\t\treturn index;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction isContinuableStopReason(stopReason: AssistantAgentMessage[\"stopReason\"]): boolean {\n\treturn stopReason === \"stop\" || stopReason === \"length\";\n}\n\nfunction isAbortedToolResult(message: ToolResultAgentMessage): boolean {\n\tif (!message.isError) return false;\n\treturn message.content.some((content) => content.type === \"text\" && /\\babort(?:ed)?\\b/i.test(content.text));\n}\n\nexport function evaluateGoalContinuation(input: GoalContinuationInput): GoalContinuationVerdict {\n\tif (!isEligibleForGoalContinuation(input)) return { kind: \"deny\", reason: \"not-eligible\" };\n\tif (input.continuationPending) return { kind: \"deny\", reason: \"single-flight\" };\n\tif (hasRepeatedNormalizedOutputHash(input.recentNormalizedOutputHashes))\n\t\treturn { kind: \"deny\", reason: \"repetition\" };\n\tif (isUnattendedBudgetExhausted(input)) return { kind: \"deny\", reason: \"unattended\" };\n\tif (input.consecutiveContinuations >= GOAL_CONTINUATION_CAP) {\n\t\treturn { kind: \"deny\", reason: \"cap\" };\n\t}\n\tif (\n\t\t(input.path === \"immediate\" || input.path === \"userGrace\") &&\n\t\tinput.lastContinuationSignature !== undefined &&\n\t\tinput.lastContinuationSignature === input.currentSignature\n\t) {\n\t\treturn { kind: \"deny\", reason: \"stale\" };\n\t}\n\tif (input.lastStopReason === \"length\") {\n\t\tif (input.consecutiveLengthRecoveries >= GOAL_LENGTH_RECOVERY_LIMIT) {\n\t\t\treturn { kind: \"deny\", reason: \"length-exhausted\" };\n\t\t}\n\t\treturn { kind: \"continue\", prompt: \"minimal\", stallNotice: shouldShowStallNotice(input) };\n\t}\n\treturn { kind: \"continue\", prompt: \"full\", stallNotice: shouldShowStallNotice(input) };\n}\n\n/** Produces a stable, whitespace-insensitive representation for repetition detection. */\nexport function normalizeAssistantText(text: string): string {\n\treturn text.toLowerCase().replace(/\\s+/g, \" \").trim();\n}\n\n/** Hashes normalized assistant text with the deterministic 32-bit FNV-1a algorithm. */\nexport function hashAssistantText(text: string): string {\n\tconst normalized = normalizeAssistantText(text);\n\tlet hash = 0x811c9dc5;\n\tfor (let index = 0; index < normalized.length; index++) {\n\t\thash ^= normalized.charCodeAt(index);\n\t\thash = Math.imul(hash, 0x01000193);\n\t}\n\treturn (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/**\n * Identifies the observable goal progress for stale-continuation admission.\n * Deliberately excludes goal.updatedAt because usage accounting updates it after every agent turn.\n */\nexport function buildGoalContinuationSignature(\n\tgoal: Pick<Goal, \"id\">,\n\topenTodos: number,\n\ttotalTodos: number,\n\tlastAssistantTextHash: string,\n): string {\n\treturn `${goal.id}:${openTodos}/${totalTodos}:${lastAssistantTextHash}`;\n}\n\nexport function hasGoalContinuationProgress(\n\tinput: Pick<GoalContinuationInput, \"lastContinuationSignature\" | \"currentSignature\">,\n): boolean {\n\treturn (\n\t\tinput.lastContinuationSignature !== undefined &&\n\t\tinput.currentSignature !== undefined &&\n\t\tinput.lastContinuationSignature !== input.currentSignature\n\t);\n}\n\nexport function continuationTurnUsedTools(messages: readonly AgentMessage[]): boolean {\n\treturn messages.some((message) => {\n\t\tif (message?.role === \"toolResult\") return true;\n\t\treturn message?.role === \"assistant\" && message.content.some((content) => content.type === \"toolCall\");\n\t});\n}\n\nfunction isEligibleForGoalContinuation(input: GoalContinuationInput): boolean {\n\tif (input.goal?.status !== \"active\" || input.hasPendingMessages) return false;\n\tif (input.path === \"systemRecovery\" || input.path === \"providerRecovery\") return true;\n\tif (input.path === \"immediate\") {\n\t\treturn input.lastStopReason !== undefined && isContinuableStopReason(input.lastStopReason);\n\t}\n\treturn input.isIdle;\n}\n\n/** Monitor-delayed deliveries are exempt: armed-wake waiting is by-design and rate-limited by the cache-aware timer. */\nfunction isUnattendedBudgetExhausted(input: GoalContinuationInput): boolean {\n\tif (input.path === \"monitorDelayed\") return false;\n\treturn (input.goal?.unattendedContinuations ?? 0) >= GOAL_UNATTENDED_CONTINUATION_LIMIT;\n}\n\nfunction hasRepeatedNormalizedOutputHash(hashes: readonly string[]): boolean {\n\tconst latestHash = hashes.at(-1);\n\tif (latestHash === undefined) return false;\n\n\tlet streak = 0;\n\tfor (let index = hashes.length - 1; index >= 0 && hashes[index] === latestHash; index--) {\n\t\tstreak += 1;\n\t}\n\treturn streak >= GOAL_REPETITION_HASH_STREAK;\n}\n\nfunction shouldShowStallNotice(input: GoalContinuationInput): boolean {\n\treturn input.toollessContinuationStreak >= GOAL_STALL_TOOLLESS_THRESHOLD;\n}\n"]}
|
|
@@ -38,7 +38,7 @@ export class GoalDirectInputLifecycle {
|
|
|
38
38
|
if (currentGoal?.id !== candidate.goalId)
|
|
39
39
|
return;
|
|
40
40
|
if (currentGoal.status === "blocked" && isMechanicalContinuationBlock(currentGoal.blockedReason)) {
|
|
41
|
-
await resetContinuationStreak(ref);
|
|
41
|
+
await resetContinuationStreak(ref, { unattended: true });
|
|
42
42
|
const reactivated = await updateGoal(ref, { status: "active" }, "user");
|
|
43
43
|
this.#dependencies.beginAgentGoalAccounting(reactivated);
|
|
44
44
|
this.#dependencies.refreshGoalUi(ctx, reactivated);
|
|
@@ -46,7 +46,7 @@ export class GoalDirectInputLifecycle {
|
|
|
46
46
|
}
|
|
47
47
|
if (currentGoal.status !== "active")
|
|
48
48
|
return;
|
|
49
|
-
const reset = await resetContinuationStreak(ref);
|
|
49
|
+
const reset = await resetContinuationStreak(ref, { unattended: true });
|
|
50
50
|
if (reset !== null)
|
|
51
51
|
this.#dependencies.refreshGoalUi(ctx, reset);
|
|
52
52
|
if (this.#suppressedLoadResumeArmed) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"direct-input-lifecycle.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/direct-input-lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAC;AAE3E,OAAO,EAAE,QAAQ,EAAE,uBAAuB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAoB3E,gFAAgF;AAChF,MAAM,OAAO,wBAAwB;IAC3B,aAAa,CAAmC;IAChD,WAAW,GAAG,IAAI,GAAG,EAAgC,CAAC;IAC/D,0BAA0B,GAAG,KAAK,CAAC;IAEnC,YAAY,YAA8C;QACzD,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;IACnC,CAAC;IAED,KAAK;QACJ,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,0BAA0B,GAAG,KAAK,CAAC;IACzC,CAAC;IAED,iFAAiF;IACjF,uBAAuB;QACtB,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,KAAiB,EAAE,GAAqB;QACrD,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;YAAE,OAAO;QAEzC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1D,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;QAClE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAA4B,EAAE,GAAqB;QACtE,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACtD,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO;QACpC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,KAAK,CAAC,WAAW,KAAK,QAAQ,CAAC;QACnF,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACvE,IAAI,CAAC,QAAQ,IAAI,SAAS,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO;QAEnD,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACjD,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,WAAW,EAAE,EAAE,KAAK,SAAS,CAAC,MAAM;YAAE,OAAO;QAEjD,IAAI,WAAW,CAAC,MAAM,KAAK,SAAS,IAAI,6BAA6B,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,CAAC;YAClG,MAAM,uBAAuB,CAAC,GAAG,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"direct-input-lifecycle.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/direct-input-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionContext, InputDispositionEvent, InputEvent } from \"../../types.ts\";\nimport { isMechanicalContinuationBlock } from \"./continuation-recovery.ts\";\nimport type { MonitorAwareGoalContinuation } from \"./monitor-continuation.ts\";\nimport { readGoal, resetContinuationStreak, updateGoal } from \"./store.ts\";\nimport type { Goal, GoalStoreRef } from \"./types.ts\";\n\ntype DirectInputCandidate = {\n\treadonly goalId: string | null;\n};\n\ntype DirectInputLifecycleDependencies = {\n\treadonly monitor: MonitorAwareGoalContinuation;\n\treadonly goalStoreRef: (ctx: ExtensionContext) => GoalStoreRef;\n\treadonly beginAgentGoalAccounting: (goal: Goal) => void;\n\treadonly refreshGoalUi: (ctx: ExtensionContext, goal: Goal) => void;\n\t/**\n\t * One-shot resume fired when the first user message after a suppressed\n\t * flooded load is accepted. The load-time suppression notice tells the user\n\t * to \"send a message to resume\"; this callback is what keeps that promise.\n\t */\n\treadonly resumeAfterSuppressedLoad?: (ctx: ExtensionContext, goal: Goal) => Promise<void>;\n};\n\n/** Correlates raw input with admission before changing persisted Goal state. */\nexport class GoalDirectInputLifecycle {\n\treadonly #dependencies: DirectInputLifecycleDependencies;\n\treadonly #candidates = new Map<string, DirectInputCandidate>();\n\t#suppressedLoadResumeArmed = false;\n\n\tconstructor(dependencies: DirectInputLifecycleDependencies) {\n\t\tthis.#dependencies = dependencies;\n\t}\n\n\treset(): void {\n\t\tthis.#candidates.clear();\n\t\tthis.#suppressedLoadResumeArmed = false;\n\t}\n\n\t/** Arms the one-shot resume for a goal parked by load-time flood suppression. */\n\tarmSuppressedLoadResume(): void {\n\t\tthis.#suppressedLoadResumeArmed = true;\n\t}\n\n\tasync onInput(event: InputEvent, ctx: ExtensionContext): Promise<void> {\n\t\tif (event.source === \"extension\") return;\n\n\t\tthis.#dependencies.monitor.holdDirectInput(event.inputId);\n\t\tthis.#candidates.set(event.inputId, { goalId: null });\n\t\tconst goal = await readGoal(this.#dependencies.goalStoreRef(ctx));\n\t\tthis.#candidates.set(event.inputId, { goalId: goal?.id ?? null });\n\t}\n\n\tasync onDisposition(event: InputDispositionEvent, ctx: ExtensionContext): Promise<void> {\n\t\tconst candidate = this.#candidates.get(event.inputId);\n\t\tif (candidate === undefined) return;\n\t\tthis.#candidates.delete(event.inputId);\n\t\tconst accepted = event.disposition === \"started\" || event.disposition === \"queued\";\n\t\tthis.#dependencies.monitor.resolveDirectInput(event.inputId, accepted);\n\t\tif (!accepted || candidate.goalId === null) return;\n\n\t\tconst ref = this.#dependencies.goalStoreRef(ctx);\n\t\tconst currentGoal = await readGoal(ref);\n\t\tif (currentGoal?.id !== candidate.goalId) return;\n\n\t\tif (currentGoal.status === \"blocked\" && isMechanicalContinuationBlock(currentGoal.blockedReason)) {\n\t\t\tawait resetContinuationStreak(ref, { unattended: true });\n\t\t\tconst reactivated = await updateGoal(ref, { status: \"active\" }, \"user\");\n\t\t\tthis.#dependencies.beginAgentGoalAccounting(reactivated);\n\t\t\tthis.#dependencies.refreshGoalUi(ctx, reactivated);\n\t\t\treturn;\n\t\t}\n\n\t\tif (currentGoal.status !== \"active\") return;\n\t\tconst reset = await resetContinuationStreak(ref, { unattended: true });\n\t\tif (reset !== null) this.#dependencies.refreshGoalUi(ctx, reset);\n\t\tif (this.#suppressedLoadResumeArmed) {\n\t\t\tthis.#suppressedLoadResumeArmed = false;\n\t\t\tawait this.#dependencies.resumeAfterSuppressedLoad?.(ctx, reset ?? currentGoal);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type { Goal, GoalStatus, GoalToolResponse } from "./types.ts";
|
|
1
|
+
import type { Goal, GoalStatus, GoalToolResponse, GoalToolSnapshot } from "./types.ts";
|
|
2
2
|
export declare function formatGoalElapsedSeconds(value: number): string;
|
|
3
3
|
export declare function formatTokensCompact(value: number): string;
|
|
4
4
|
export declare function goalStatusLabel(status: GoalStatus): string;
|
|
5
5
|
export declare function formatGoalForTool(goal: Goal | null): string;
|
|
6
|
+
export type GoalToolRenderDetails = {
|
|
7
|
+
goal: GoalToolSnapshot | null;
|
|
8
|
+
notice?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function goalToolRenderDetails(goal: Goal | null, notice?: string): GoalToolRenderDetails;
|
|
6
11
|
export declare function goalToolResponse(goal: Goal | null): GoalToolResponse;
|
|
7
12
|
export declare function formatGoalToolResponse(goal: Goal | null, notice?: string): string;
|
|
8
13
|
//# sourceMappingURL=format.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEvF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAiB9D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKzD;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAW1D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,MAAM,CAW3D;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,qBAAqB,CAK/F;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,gBAAgB,CAEpE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjF"}
|
|
@@ -51,6 +51,12 @@ export function formatGoalForTool(goal) {
|
|
|
51
51
|
lines.push(`Completed at: ${new Date(goal.completedAt * 1000).toISOString()}`);
|
|
52
52
|
return lines.join("\n");
|
|
53
53
|
}
|
|
54
|
+
export function goalToolRenderDetails(goal, notice) {
|
|
55
|
+
return {
|
|
56
|
+
goal: goal === null ? null : goalToolSnapshot(goal),
|
|
57
|
+
...(notice === undefined ? {} : { notice }),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
54
60
|
export function goalToolResponse(goal) {
|
|
55
61
|
return { goal: goal === null ? null : goalToolSnapshot(goal) };
|
|
56
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/format.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Goal, GoalStatus, GoalToolResponse, GoalToolSnapshot } from \"./types.ts\";\n\nexport function formatGoalElapsedSeconds(value: number): string {\n\tconst seconds = Math.max(0, Math.trunc(value));\n\tif (seconds < 60) return `${seconds}s`;\n\n\tconst minutes = Math.trunc(seconds / 60);\n\tif (minutes < 60) return `${minutes}m`;\n\n\tconst hours = Math.trunc(minutes / 60);\n\tconst remainingMinutes = minutes % 60;\n\tif (hours >= 24) {\n\t\tconst days = Math.trunc(hours / 24);\n\t\tconst remainingHours = hours % 24;\n\t\treturn `${days}d ${remainingHours}h ${remainingMinutes}m`;\n\t}\n\n\tif (remainingMinutes === 0) return `${hours}h`;\n\treturn `${hours}h ${remainingMinutes}m`;\n}\n\nexport function formatTokensCompact(value: number): string {\n\tconst abs = Math.abs(value);\n\tif (abs >= 1_000_000) return `${formatOneDecimal(value / 1_000_000)}M`;\n\tif (abs >= 1_000) return `${formatOneDecimal(value / 1_000)}K`;\n\treturn `${Math.trunc(value)}`;\n}\n\nexport function goalStatusLabel(status: GoalStatus): string {\n\tswitch (status) {\n\t\tcase \"active\":\n\t\t\treturn \"active\";\n\t\tcase \"paused\":\n\t\t\treturn \"paused\";\n\t\tcase \"blocked\":\n\t\t\treturn \"blocked\";\n\t\tcase \"complete\":\n\t\t\treturn \"complete\";\n\t}\n}\n\nexport function formatGoalForTool(goal: Goal | null): string {\n\tif (!goal) return \"No active goal is set.\";\n\tconst lines = [\n\t\t`Objective: ${goal.objective}`,\n\t\t`Status: ${goalStatusLabel(goal.status)}`,\n\t\t`Time used: ${formatGoalElapsedSeconds(goal.timeUsedSeconds)}`,\n\t\t`Tokens used: ${formatTokensCompact(goal.tokensUsed)}`,\n\t];\n\tif (goal.blockedReason) lines.push(`Blocked reason: ${goal.blockedReason}`);\n\tif (goal.completedAt) lines.push(`Completed at: ${new Date(goal.completedAt * 1000).toISOString()}`);\n\treturn lines.join(\"\\n\");\n}\n\nexport function goalToolResponse(goal: Goal | null): GoalToolResponse {\n\treturn { goal: goal === null ? null : goalToolSnapshot(goal) };\n}\n\nexport function formatGoalToolResponse(goal: Goal | null, notice?: string): string {\n\tconst response = JSON.stringify(goalToolResponse(goal), null, 2);\n\treturn notice === undefined ? response : `${response}\\n${notice}`;\n}\n\nfunction goalToolSnapshot(goal: Goal): GoalToolSnapshot {\n\treturn {\n\t\tthreadId: goal.threadId,\n\t\tobjective: goal.objective,\n\t\tstatus: goal.status,\n\t\ttokensUsed: goal.tokensUsed,\n\t\ttimeUsedSeconds: goal.timeUsedSeconds,\n\t\tcreatedAt: goal.createdAt,\n\t\tupdatedAt: goal.updatedAt,\n\t\t...(goal.blockedReason === undefined ? {} : { blockedReason: goal.blockedReason }),\n\t\t...(goal.blockedAt === undefined ? {} : { blockedAt: goal.blockedAt }),\n\t};\n}\n\nfunction formatOneDecimal(value: number): string {\n\tconst rounded = value.toFixed(1);\n\treturn rounded.endsWith(\".0\") ? rounded.slice(0, -2) : rounded;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/format.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,wBAAwB,CAAC,KAAa;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/C,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IAEvC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACzC,IAAI,OAAO,GAAG,EAAE;QAAE,OAAO,GAAG,OAAO,GAAG,CAAC;IAEvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACvC,MAAM,gBAAgB,GAAG,OAAO,GAAG,EAAE,CAAC;IACtC,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC;QACpC,MAAM,cAAc,GAAG,KAAK,GAAG,EAAE,CAAC;QAClC,OAAO,GAAG,IAAI,KAAK,cAAc,KAAK,gBAAgB,GAAG,CAAC;IAC3D,CAAC;IAED,IAAI,gBAAgB,KAAK,CAAC;QAAE,OAAO,GAAG,KAAK,GAAG,CAAC;IAC/C,OAAO,GAAG,KAAK,KAAK,gBAAgB,GAAG,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAChD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,GAAG,IAAI,SAAS;QAAE,OAAO,GAAG,gBAAgB,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC;IACvE,IAAI,GAAG,IAAI,KAAK;QAAE,OAAO,GAAG,gBAAgB,CAAC,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC;IAC/D,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAkB;IACjD,QAAQ,MAAM,EAAE,CAAC;QAChB,KAAK,QAAQ;YACZ,OAAO,QAAQ,CAAC;QACjB,KAAK,QAAQ;YACZ,OAAO,QAAQ,CAAC;QACjB,KAAK,SAAS;YACb,OAAO,SAAS,CAAC;QAClB,KAAK,UAAU;YACd,OAAO,UAAU,CAAC;IACpB,CAAC;AACF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAiB;IAClD,IAAI,CAAC,IAAI;QAAE,OAAO,wBAAwB,CAAC;IAC3C,MAAM,KAAK,GAAG;QACb,cAAc,IAAI,CAAC,SAAS,EAAE;QAC9B,WAAW,eAAe,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;QACzC,cAAc,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE;QAC9D,gBAAgB,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE;KACtD,CAAC;IACF,IAAI,IAAI,CAAC,aAAa;QAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5E,IAAI,IAAI,CAAC,WAAW;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACrG,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAOD,MAAM,UAAU,qBAAqB,CAAC,IAAiB,EAAE,MAAe;IACvE,OAAO;QACN,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC;QACnD,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KAC3C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAAiB;IACjD,OAAO,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAiB,EAAE,MAAe;IACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACjE,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,MAAM,EAAE,CAAC;AACnE,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAU;IACnC,OAAO;QACN,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC;QAClF,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC;KACtE,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACtC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACjC,OAAO,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAChE,CAAC","sourcesContent":["import type { Goal, GoalStatus, GoalToolResponse, GoalToolSnapshot } from \"./types.ts\";\n\nexport function formatGoalElapsedSeconds(value: number): string {\n\tconst seconds = Math.max(0, Math.trunc(value));\n\tif (seconds < 60) return `${seconds}s`;\n\n\tconst minutes = Math.trunc(seconds / 60);\n\tif (minutes < 60) return `${minutes}m`;\n\n\tconst hours = Math.trunc(minutes / 60);\n\tconst remainingMinutes = minutes % 60;\n\tif (hours >= 24) {\n\t\tconst days = Math.trunc(hours / 24);\n\t\tconst remainingHours = hours % 24;\n\t\treturn `${days}d ${remainingHours}h ${remainingMinutes}m`;\n\t}\n\n\tif (remainingMinutes === 0) return `${hours}h`;\n\treturn `${hours}h ${remainingMinutes}m`;\n}\n\nexport function formatTokensCompact(value: number): string {\n\tconst abs = Math.abs(value);\n\tif (abs >= 1_000_000) return `${formatOneDecimal(value / 1_000_000)}M`;\n\tif (abs >= 1_000) return `${formatOneDecimal(value / 1_000)}K`;\n\treturn `${Math.trunc(value)}`;\n}\n\nexport function goalStatusLabel(status: GoalStatus): string {\n\tswitch (status) {\n\t\tcase \"active\":\n\t\t\treturn \"active\";\n\t\tcase \"paused\":\n\t\t\treturn \"paused\";\n\t\tcase \"blocked\":\n\t\t\treturn \"blocked\";\n\t\tcase \"complete\":\n\t\t\treturn \"complete\";\n\t}\n}\n\nexport function formatGoalForTool(goal: Goal | null): string {\n\tif (!goal) return \"No active goal is set.\";\n\tconst lines = [\n\t\t`Objective: ${goal.objective}`,\n\t\t`Status: ${goalStatusLabel(goal.status)}`,\n\t\t`Time used: ${formatGoalElapsedSeconds(goal.timeUsedSeconds)}`,\n\t\t`Tokens used: ${formatTokensCompact(goal.tokensUsed)}`,\n\t];\n\tif (goal.blockedReason) lines.push(`Blocked reason: ${goal.blockedReason}`);\n\tif (goal.completedAt) lines.push(`Completed at: ${new Date(goal.completedAt * 1000).toISOString()}`);\n\treturn lines.join(\"\\n\");\n}\n\nexport type GoalToolRenderDetails = {\n\tgoal: GoalToolSnapshot | null;\n\tnotice?: string;\n};\n\nexport function goalToolRenderDetails(goal: Goal | null, notice?: string): GoalToolRenderDetails {\n\treturn {\n\t\tgoal: goal === null ? null : goalToolSnapshot(goal),\n\t\t...(notice === undefined ? {} : { notice }),\n\t};\n}\n\nexport function goalToolResponse(goal: Goal | null): GoalToolResponse {\n\treturn { goal: goal === null ? null : goalToolSnapshot(goal) };\n}\n\nexport function formatGoalToolResponse(goal: Goal | null, notice?: string): string {\n\tconst response = JSON.stringify(goalToolResponse(goal), null, 2);\n\treturn notice === undefined ? response : `${response}\\n${notice}`;\n}\n\nfunction goalToolSnapshot(goal: Goal): GoalToolSnapshot {\n\treturn {\n\t\tthreadId: goal.threadId,\n\t\tobjective: goal.objective,\n\t\tstatus: goal.status,\n\t\ttokensUsed: goal.tokensUsed,\n\t\ttimeUsedSeconds: goal.timeUsedSeconds,\n\t\tcreatedAt: goal.createdAt,\n\t\tupdatedAt: goal.updatedAt,\n\t\t...(goal.blockedReason === undefined ? {} : { blockedReason: goal.blockedReason }),\n\t\t...(goal.blockedAt === undefined ? {} : { blockedAt: goal.blockedAt }),\n\t};\n}\n\nfunction formatOneDecimal(value: number): string {\n\tconst rounded = value.toFixed(1);\n\treturn rounded.endsWith(\".0\") ? rounded.slice(0, -2) : rounded;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle-helpers.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/lifecycle-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAGlE,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAGN,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAE5B,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"lifecycle-helpers.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/lifecycle-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAGlE,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAGN,KAAK,qBAAqB,EAC1B,KAAK,uBAAuB,EAE5B,MAAM,mBAAmB,CAAC;AAY3B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,KAAK,iCAAiC,GAAG,OAAO,CAAC,uBAAuB,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC;AAEhG,KAAK,+BAA+B,GAAG;IACtC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IACpD,QAAQ,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,iCAAiC,KAAK,MAAM,CAAC;IACzE,QAAQ,CAAC,uBAAuB,EAAE,MAAM,IAAI,CAAC;CAC7C,CAAC;AAEF,MAAM,MAAM,+BAA+B,GAAG;IAC7C,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,IAAI,CAAC;CAC7C,CAAC;AAQF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACpC,GAAG,EAAE,gBAAgB,EACrB,kBAAkB,EAAE,MAAM,EAC1B,IAAI,EAAE,IAAI,GAAG,IAAI,GACf,IAAI,IAAI,IAAI,CASd;AAED,sFAAsF;AACtF,wBAAsB,6BAA6B,CAClD,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,+BAA+B,GACtC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAiBtB;AAED,mHAAmH;AACnH,wBAAsB,qBAAqB,CAC1C,EAAE,EAAE,YAAY,EAChB,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,+BAA+B,GACtC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAuBtB;AAED,wBAAgB,qCAAqC,CACpD,GAAG,EAAE,gBAAgB,EACrB,IAAI,EAAE,IAAI,EACV,iBAAiB,EAAE,MAAM,GACvB,MAAM,CAKR;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAM3E;AA6DD,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK7E"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { GOAL_CONTINUATION_MESSAGE_TYPE } from "../../../messages.js";
|
|
2
2
|
import { getLatestPhasesFromBranchEntries } from "../todotools/state.js";
|
|
3
3
|
import { buildGoalContinuationSignature, evaluateGoalContinuation, hashAssistantText, } from "./continuation.js";
|
|
4
|
-
import { CONTINUATION_CAP_BLOCKED_REASON, continuationCapRecoveryHint, LENGTH_EXHAUSTED_BLOCKED_REASON, REPETITION_BLOCKED_REASON, } from "./continuation-recovery.js";
|
|
4
|
+
import { CONTINUATION_CAP_BLOCKED_REASON, continuationCapRecoveryHint, LENGTH_EXHAUSTED_BLOCKED_REASON, REPETITION_BLOCKED_REASON, UNATTENDED_CONTINUATION_BLOCKED_REASON, } from "./continuation-recovery.js";
|
|
5
5
|
import { buildContinuationPrompt } from "./prompt.js";
|
|
6
6
|
import { recordContinuationDelivered, updateGoal } from "./store.js";
|
|
7
7
|
import { goalStoreRef } from "./store-ref.js";
|
|
@@ -33,7 +33,7 @@ export async function admitAndQueueGoalContinuation(pi, ctx, goal, options) {
|
|
|
33
33
|
if (options.input.currentSignature === undefined) {
|
|
34
34
|
throw new Error("Cannot queue a goal continuation without a progress signature");
|
|
35
35
|
}
|
|
36
|
-
const recordedGoal = await recordContinuationDelivered(goalStoreRef(ctx.sessionManager, ctx.cwd), options.input.currentSignature, goal.id);
|
|
36
|
+
const recordedGoal = await recordContinuationDelivered(goalStoreRef(ctx.sessionManager, ctx.cwd), options.input.currentSignature, goal.id, { countUnattended: options.input.path !== "monitorDelayed" });
|
|
37
37
|
if (recordedGoal === null)
|
|
38
38
|
return null;
|
|
39
39
|
options.markContinuationPending();
|
|
@@ -100,6 +100,7 @@ async function handleDeniedContinuation(pi, ctx, goal, input, reason) {
|
|
|
100
100
|
goalId: goal.id,
|
|
101
101
|
reason,
|
|
102
102
|
count: input.consecutiveContinuations,
|
|
103
|
+
unattendedContinuations: goal.unattendedContinuations ?? 0,
|
|
103
104
|
});
|
|
104
105
|
return blocked;
|
|
105
106
|
}
|
|
@@ -107,6 +108,8 @@ function blockedReasonForContinuationGuard(reason) {
|
|
|
107
108
|
switch (reason) {
|
|
108
109
|
case "cap":
|
|
109
110
|
return CONTINUATION_CAP_BLOCKED_REASON;
|
|
111
|
+
case "unattended":
|
|
112
|
+
return UNATTENDED_CONTINUATION_BLOCKED_REASON;
|
|
110
113
|
case "repetition":
|
|
111
114
|
return REPETITION_BLOCKED_REASON;
|
|
112
115
|
case "length-exhausted":
|