@gajae-code/coding-agent 0.11.9 → 0.11.10
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 +18 -1
- package/dist/types/config/model-registry.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +16 -8
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +8 -1
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +74 -5
- package/dist/types/sdk/session.d.ts +7 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +1 -1
- package/dist/types/task/provider-retry-status.d.ts +13 -0
- package/dist/types/task/types.d.ts +3 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +1 -0
- package/src/cli/notify-cli.ts +1 -0
- package/src/config/model-profiles.ts +7 -7
- package/src/config/model-registry.ts +4 -2
- package/src/eval/js/tool-bridge.ts +1 -1
- package/src/gjc-runtime/deep-interview-draft.ts +2 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +16 -1
- package/src/gjc-runtime/state-writer.ts +1 -1
- package/src/hooks/skill-state.ts +23 -1
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/modes/controllers/selector-controller.ts +38 -8
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/architect.md +8 -1
- package/src/prompts/agents/critic.md +8 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/sdk/broker/identity.ts +22 -12
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/index.ts +60 -5
- package/src/sdk/bus/notification-service.ts +19 -5
- package/src/sdk/bus/operator-runtime.ts +31 -5
- package/src/sdk/bus/telegram-cli.ts +6 -13
- package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +13 -7
- package/src/sdk/bus/telegram-daemon-control.ts +14 -5
- package/src/sdk/bus/telegram-daemon.ts +667 -118
- package/src/sdk/protocol/operation-inventory.generated.json +11 -0
- package/src/sdk/session.ts +12 -0
- package/src/session/agent-session.ts +54 -10
- package/src/session/internal/managed-session-scope.ts +133 -117
- package/src/skill-state/workflow-mutation-guard.ts +41 -7
- package/src/task/executor.ts +141 -39
- package/src/task/provider-retry-status.ts +88 -0
- package/src/task/render.ts +45 -26
- package/src/task/types.ts +3 -0
- package/src/tools/bash-allowed-prefixes.ts +45 -1
- package/src/tools/index.ts +3 -1
- package/src/tools/subagent-render.ts +74 -16
- package/src/tools/subagent.ts +2 -0
- package/vendor/insane-search/MANIFEST.json +4 -1
- package/vendor/insane-search/engine/bias_check.py +5 -0
- package/vendor/insane-search/engine/fetch_chain.py +67 -1
- package/vendor/insane-search/engine/transport.py +13 -2
- package/vendor/insane-search/engine/validators.py +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,21 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.10] - 2026-07-25
|
|
6
|
+
### Changed
|
|
7
|
+
|
|
8
|
+
- The built-in `claude-opus`, `opus-codex`, and `fable-opus-codex` presets now use `anthropic/claude-opus-5` instead of `anthropic/claude-opus-4-8`, with effort suffixes preserved; `packages/ai/src/models.json` was regenerated so `anthropic/claude-opus-5` resolves; non-opus roles (`anthropic/claude-sonnet-5` executor/planner overrides, codex and fable roles) are unchanged.
|
|
9
|
+
|
|
5
10
|
## [0.11.9] - 2026-07-24
|
|
6
11
|
|
|
7
12
|
### Fixed
|
|
8
13
|
|
|
14
|
+
- Restricted role-agent `bash` now accepts literal mid-word tildes, so git revision syntax such as `git diff HEAD~1` no longer has to be quoted. Bash performs tilde expansion only at the start of a word, so word-initial forms (`~`, `~/path`, `~user`) remain blocked.
|
|
15
|
+
- Restricted role-agent `bash` now rejects unquoted tildes at every bash expansion position inside assignment words, including the compound `name+=value` form, so `A=~`, `A+=~`, `foo=~root/bar`, `A=x:~`, `A+=x:~`, and repeated colon segments such as `a=x:~:y:~` fail closed. Tildes bash does not expand — mid-word git revisions (`HEAD~1`), non-assignment words (`--opt=~`, `1abc=~`, `a++=~`, `a+b=~`), and quoted forms — remain allowed (#3117).
|
|
16
|
+
- Read-only role agents (`architect`, `planner`, `critic`) now receive the `irc` coordination tool and a read-only git prefix set (`status`, `log`, `show`, `diff`, `blame`, `rev-parse`, `ls-files`) in restricted bash; mutating git and arbitrary shell stay blocked. `irc` also stays in the initial active tool set for subagents whenever the parent runtime reports IRC availability, instead of costing a discovery round-trip (#3109).
|
|
17
|
+
- The restricted-bash workflow guard now allows `/dev/null` redirects (so `cmd 2>/dev/null` is no longer treated as a repository write during planning phases) while keeping `/dev/stdout`, `/dev/stderr`, and `/dev/fd/<n>` blocked, failing closed on `exec` redirections, and recognizing `>|`, `>&path`, `<>`, path-qualified writers, and every `dd of=` operand. Hook-seeded deep-interview state is now gated on `isNativeDeepInterviewV1` and seeded as a native v1 envelope, so typed operations no longer fail with `DI_STATE_SCHEMA_INVALID` and run the interview manually (#3127).
|
|
18
|
+
- The vendored `insane-search` engine no longer treats a `429` as terminal: rate-limited probe and grid candidates back off (linear escalation honoring `Retry-After`, hard-capped at 30s) and continue through grid diversity and browser fallback. The backoff base from `INSANE_RATE_LIMIT_BACKOFF_S` is validated and clamped, so non-numeric, `NaN`, infinite, negative, or huge values can no longer raise, hang, or defeat a per-attempt deadline, and sleeps stay short enough to honor cancellation (#3131).
|
|
19
|
+
- ACP sessions now apply execution permission decisions to eval calls and to tools invoked from JavaScript or Python eval contexts, while non-ACP session behavior remains unchanged.
|
|
20
|
+
- Interactive prompt cancellation now reaches API-key preflight through `ModelRegistry`, allowing aborted submissions to clear immediately even while a shared credential-usage request continues in the background.
|
|
9
21
|
- Alibaba Token Plan canonical first-event timeouts now surface without session retry/fallback replay and are not internally retried by auto-compaction, preventing repeated provider usage (#3026).
|
|
22
|
+
- Delegated-task and subagent status surfaces now distinguish provider recovery from normal running, identify first-event versus idle-stream stalls, show retry budget and provider-progress age, and aggregate concurrent degradation by provider (#3071).
|
|
23
|
+
- Telegram notification daemon ownership hardening (#3048): Bot API outcomes now share one honest classifier so both the initiating `429` response and cooldown-suppressed calls settle retryably instead of being lost or falsely rejected, including selected acknowledgements; exclusive operator work is registered before its callback can throw; notification health degrades corrupt daemon-state JSON to a warning; root-registration ownership tokens propagate through injected and built-in ensure, rollback, reconciliation, teardown, and abandoned-startup cleanup seams, with token-bearing rows refusing tokenless cleanup while genuinely legacy rows retain root-match behavior; and initial daemon readiness is published only after the matching heartbeat sidecar rename is durable, so no waiter can attach during the proof window.
|
|
24
|
+
- `/new`, `fork()`, handoff, `/resume`, and branch/tree-jump transitions now complete verified managed `local://` legacy-root migration for the successor session identity *before* that identity is published to the agent, the workflow-gate emitter, or extension hooks, so no observer can resolve `local://` against an ungated root across the gate's `await` boundary. Matches cold-start `createAgentSession()` (#2797) and extends `/resume` (#2925). Sending a prompt right after `/new` no longer fails with "local:// legacy migration must complete before path resolution".
|
|
10
25
|
|
|
11
26
|
- Telegram notification topics now fence malformed successful `createForumTopic` responses per session endpoint, preventing repeated ambiguous topic creation while keeping explicit Bot API failures retryable.
|
|
12
27
|
- Windows managed-session resume no longer reports `durability_failed` when Bun rejects `fsync` on the read-only descriptor used to revalidate an existing canonical binding; Windows now uses an owner-writable descriptor for that durability fence while retaining no-follow and pre/post identity/content checks.
|
|
13
|
-
- SDK daemon CLI end-to-end tests now
|
|
28
|
+
- SDK daemon CLI end-to-end tests now capture spawned child stdout and stderr through temporary files instead of pipes, removing the CI pipe teardown race that replaced the product exit contract with SIGPIPE status 141 (#3024).
|
|
14
29
|
- Interactive launch bootstrap is now suppressed for parser-accepted `--print=`, `--help=`, and `--version=` equals forms, keeping non-interactive output free of the warming-workspace preamble on TTYs.
|
|
15
30
|
- Managed legacy-session artifact migration now accepts up to 50,000 files, processes copy work in bounded batches, and reports capacity exhaustion separately from unsafe artifact topology (#2935).
|
|
16
31
|
- Kimi Code first-event timeouts now surface after the provider's continuous first-event wait instead of replaying the full request from zero.
|
|
17
32
|
|
|
18
33
|
- Rejected subagent schema payloads now retain their complete structured data in canonical output artifacts; inline results remain bounded while `agent://` output stays lossless (#2894).
|
|
19
34
|
- Managed legacy-session artifact migration now validates Windows directory roots from the native tree snapshot, tolerates only lazy metadata on plain Windows directories, and replays both clean and cleanup-pending detaches while retaining fail-closed receipt validation. Canonical binding durability sync uses a writable no-follow handle on Windows NTFS stacks that reject `FlushFileBuffers` on read-only handles (#3015, #2913).
|
|
35
|
+
- A single managed session tombstone that fails to reconcile (e.g. an artifact directory identity mismatch) no longer blocks resume or delete of every other session in the same managed scope; the failure is isolated to that tombstone and logged, unless it belongs to the session currently being opened, which still fails closed.
|
|
36
|
+
|
|
20
37
|
## [0.11.8] - 2026-07-23
|
|
21
38
|
### Added
|
|
22
39
|
|
|
@@ -387,12 +387,14 @@ export declare class ModelRegistry {
|
|
|
387
387
|
*/
|
|
388
388
|
getApiKey(model: Model<Api>, sessionId?: string, options?: {
|
|
389
389
|
credentialSelector?: AuthCredentialSelector;
|
|
390
|
+
signal?: AbortSignal;
|
|
390
391
|
}): Promise<string | undefined>;
|
|
391
392
|
/**
|
|
392
393
|
* Get API key for a provider (e.g., "openai").
|
|
393
394
|
*/
|
|
394
395
|
getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string, options?: {
|
|
395
396
|
credentialSelector?: AuthCredentialSelector;
|
|
397
|
+
signal?: AbortSignal;
|
|
396
398
|
}): Promise<string | undefined>;
|
|
397
399
|
/**
|
|
398
400
|
* Check if a model is using OAuth credentials (subscription).
|
|
@@ -183,6 +183,7 @@ export type StrictMutationReadResult = {
|
|
|
183
183
|
};
|
|
184
184
|
export declare function readExistingStateForMutation(filePath: string): Promise<StrictMutationReadResult>;
|
|
185
185
|
export declare function workflowEnvelopeContentSha256(value: unknown): string;
|
|
186
|
+
export declare function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean;
|
|
186
187
|
export declare function verifyWorkflowEnvelopeReceiptValue(value: unknown, filePath: string): WorkflowReceiptVerification;
|
|
187
188
|
export declare function stampWorkflowEnvelopeChecksum<T>(value: T, filePath: string, computedAt?: string): T;
|
|
188
189
|
export declare function detectWorkflowEnvelopeIntegrityMismatch(filePath: string): Promise<WorkflowEnvelopeIntegrityMismatch | undefined>;
|
|
@@ -5,7 +5,7 @@ import type { InteractiveModeContext, OAuthSelectorOptions } from "../../modes/t
|
|
|
5
5
|
import { getNotificationConfig, maskToken } from "../../sdk/bus/config";
|
|
6
6
|
import { proposedTelegramIdentity, reconcileCommittedTelegramConfiguration, removeTelegramConfiguration, saveTelegramInactive } from "../../sdk/bus/notification-orchestration";
|
|
7
7
|
import { buildNotificationStatusReport, checkNotificationHealth, recoverNotifications, sanitizeDiagnostic, sendNotificationTest } from "../../sdk/bus/notification-service";
|
|
8
|
-
import { ensureTelegramDaemonRunningDetailed, resolveTelegramSetupPreflight, unregisterNotificationRoot } from "../../sdk/bus/telegram-daemon";
|
|
8
|
+
import { ensureTelegramDaemonRunningDetailed, readNotificationRootRegistration, resolveTelegramSetupPreflight, unregisterNotificationRoot } from "../../sdk/bus/telegram-daemon";
|
|
9
9
|
import { runTelegramSetup } from "../../sdk/bus/telegram-setup";
|
|
10
10
|
import { type CredentialAutoImportStateStore } from "../../setup/credential-auto-import";
|
|
11
11
|
import { type CommandPaletteAction } from "../components/command-palette";
|
|
@@ -28,6 +28,13 @@ export interface NotificationsEditorAdapterContext {
|
|
|
28
28
|
sessionManager: Pick<InteractiveModeContext["sessionManager"], "getCwd" | "getSessionId">;
|
|
29
29
|
notifyConfigChanged?: () => Promise<void> | void;
|
|
30
30
|
}
|
|
31
|
+
type TelegramDaemonStopResult = {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
message: string;
|
|
34
|
+
before?: {
|
|
35
|
+
health?: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
31
38
|
export interface NotificationsEditorOperationDependencies {
|
|
32
39
|
getNotificationConfig: typeof getNotificationConfig;
|
|
33
40
|
maskToken: typeof maskToken;
|
|
@@ -43,7 +50,13 @@ export interface NotificationsEditorOperationDependencies {
|
|
|
43
50
|
reconcileCommittedTelegramConfiguration: typeof reconcileCommittedTelegramConfiguration;
|
|
44
51
|
saveTelegramInactive: typeof saveTelegramInactive;
|
|
45
52
|
removeTelegramConfiguration: typeof removeTelegramConfiguration;
|
|
53
|
+
readNotificationRootRegistration: typeof readNotificationRootRegistration;
|
|
46
54
|
unregisterNotificationRoot: typeof unregisterNotificationRoot;
|
|
55
|
+
stopTelegramDaemonIfRootRegistryFenceMatches(input: {
|
|
56
|
+
settings: Settings;
|
|
57
|
+
registryFingerprint: string;
|
|
58
|
+
stop: () => Promise<TelegramDaemonStopResult>;
|
|
59
|
+
}): Promise<boolean>;
|
|
47
60
|
reloadTelegramDaemon(settings: Settings): Promise<{
|
|
48
61
|
ok: boolean;
|
|
49
62
|
message: string;
|
|
@@ -52,13 +65,7 @@ export interface NotificationsEditorOperationDependencies {
|
|
|
52
65
|
ok: boolean;
|
|
53
66
|
message: string;
|
|
54
67
|
}>;
|
|
55
|
-
stopTelegramDaemon(settings: Settings): Promise<
|
|
56
|
-
ok: boolean;
|
|
57
|
-
message: string;
|
|
58
|
-
before?: {
|
|
59
|
-
health?: string;
|
|
60
|
-
};
|
|
61
|
-
}>;
|
|
68
|
+
stopTelegramDaemon(settings: Settings): Promise<TelegramDaemonStopResult>;
|
|
62
69
|
}
|
|
63
70
|
/**
|
|
64
71
|
* Concrete service adapter for the direct Notifications settings tab. Secrets remain in this closure's
|
|
@@ -128,3 +135,4 @@ export declare class SelectorController {
|
|
|
128
135
|
showJobsOverlay(observer: JobsObserver): void;
|
|
129
136
|
showTasksPane(aggregator: TasksAggregator): void;
|
|
130
137
|
}
|
|
138
|
+
export {};
|
|
@@ -29,7 +29,7 @@ import { CursorRegistry, RevisionStore } from "../host/query";
|
|
|
29
29
|
import { type SdkStartupFailure } from "../startup-capability";
|
|
30
30
|
import { type NotificationConfig } from "./config";
|
|
31
31
|
import { NotificationSessionController } from "./session-control";
|
|
32
|
-
import { type EnsureDaemonResult } from "./telegram-daemon";
|
|
32
|
+
import { type EnsureDaemonResult, type RegisterNotificationRootResult } from "./telegram-daemon";
|
|
33
33
|
/** Where a `session_create` should run. Discriminated by `kind`. */
|
|
34
34
|
export type SessionCreateTarget = {
|
|
35
35
|
kind: "existing_path";
|
|
@@ -324,6 +324,12 @@ interface SessionRuntime {
|
|
|
324
324
|
hostStopped: boolean;
|
|
325
325
|
serverStopped: boolean;
|
|
326
326
|
brokerRegistrationReleased: boolean;
|
|
327
|
+
/** Managed Telegram root registration released during terminal teardown. */
|
|
328
|
+
notificationRootRegistration?: {
|
|
329
|
+
settings: Settings;
|
|
330
|
+
cwd: string;
|
|
331
|
+
registrationToken?: string;
|
|
332
|
+
};
|
|
327
333
|
verbosity: "lean" | "verbose";
|
|
328
334
|
sessionTag: string;
|
|
329
335
|
/** Whether the agent loop is currently running (drives the typing indicator). */
|
|
@@ -511,6 +517,7 @@ export declare function createNotificationsExtension(api: ExtensionAPI, options?
|
|
|
511
517
|
settings: Settings;
|
|
512
518
|
cwd: string;
|
|
513
519
|
sessionId: string;
|
|
520
|
+
onRegistered?: (registration: RegisterNotificationRootResult) => void;
|
|
514
521
|
}) => Promise<EnsureDaemonResult>;
|
|
515
522
|
ensureProviderDaemon?: (provider: "discord" | "slack", settings: Settings) => Promise<unknown>;
|
|
516
523
|
/** Suppress auto-delivery for a GJC-spawned child under `sessionScope=primary`. */
|
|
@@ -47,6 +47,7 @@ export declare class NotificationOperatorRuntime {
|
|
|
47
47
|
stopInterval(name: string): void;
|
|
48
48
|
stopAllIntervals(): void;
|
|
49
49
|
runExclusive(name: string, fn: () => Promise<void>): Promise<void>;
|
|
50
|
+
joinExclusive(name: string, timeoutMs: number): Promise<boolean>;
|
|
50
51
|
sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
51
52
|
now(): number;
|
|
52
53
|
}
|
|
@@ -5,12 +5,9 @@
|
|
|
5
5
|
/** Protocol version the daemon advertises in its ClientHello. */
|
|
6
6
|
export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* freshly-upgraded host must still recognize an older, still-live daemon that
|
|
12
|
-
* predates capability-gated frame enforcement and trigger a reload. Bump this
|
|
13
|
-
* on every daemon-behavior change independent of the wire version.
|
|
8
|
+
* Guarded behavior-inventory version for the current daemon build. Bump this
|
|
9
|
+
* on every guarded daemon-behavior change independent of the wire version; it
|
|
10
|
+
* does not force a live daemon reload by itself.
|
|
14
11
|
* The current development baseline already includes #2299's generation 4,
|
|
15
12
|
* incarnation fencing in generation 5, owner-lock authority in generation 6,
|
|
16
13
|
* identity-atomic transition markers in generation 7, stable signaling plus
|
|
@@ -38,5 +35,13 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
|
|
|
38
35
|
* cooldown and lazy Telegram topic lifecycle safeguards (#2956, #2960, #2984).
|
|
39
36
|
* Generation 27 refreshes retained native path and process authority semantics.
|
|
40
37
|
* Generation 28 rejects special files before retained native authority opens.
|
|
38
|
+
* Generation 29 adds serving-epoch compatibility, sidecar heartbeat, root GC,
|
|
39
|
+
* and Bot API cooldown structural fixes (#2956, #2960, #3048).
|
|
41
40
|
*/
|
|
42
|
-
export declare const DAEMON_GENERATION =
|
|
41
|
+
export declare const DAEMON_GENERATION = 29;
|
|
42
|
+
/**
|
|
43
|
+
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
|
|
44
|
+
* all builds published before this field existed; bump this to force serving
|
|
45
|
+
* convergence and reload of compatible live predecessors.
|
|
46
|
+
*/
|
|
47
|
+
export declare const SERVING_EPOCH = 2;
|
|
@@ -5,10 +5,10 @@ import { HEARTBEAT_TTL_MS } from "./daemon-paths";
|
|
|
5
5
|
import { type DaemonTransitionLock, type NotificationEndpointFile, type NotificationEndpointFileIdentity, type NotificationExactUnlinkResult } from "./notification-service";
|
|
6
6
|
import { type DaemonProcessReference } from "./telegram-daemon-control";
|
|
7
7
|
import { type TelegramSetupPreflight } from "./telegram-setup";
|
|
8
|
-
export { DAEMON_GENERATION, NOTIFICATION_PROTOCOL_VERSION } from "./telegram-daemon-contract";
|
|
8
|
+
export { DAEMON_GENERATION, NOTIFICATION_PROTOCOL_VERSION, SERVING_EPOCH } from "./telegram-daemon-contract";
|
|
9
9
|
import { type ControlServerLike, type LifecycleControlServerFactory } from "./lifecycle-control-runtime";
|
|
10
10
|
import type { OrchestratorDeps } from "./lifecycle-orchestrator";
|
|
11
|
-
import { NotificationOperatorRuntime
|
|
11
|
+
import { NotificationOperatorRuntime } from "./operator-runtime";
|
|
12
12
|
import { type AliasTable, type CallbackRoute } from "./telegram-reference";
|
|
13
13
|
import { type TopicEndpointBinding } from "./topic-registry";
|
|
14
14
|
export type EnsureDaemonResult = "owner_spawned" | "attached" | "disabled" | "blocked";
|
|
@@ -41,6 +41,8 @@ export interface DaemonState {
|
|
|
41
41
|
* notification wire protocol version. Absent on pre-generation state.
|
|
42
42
|
*/
|
|
43
43
|
generation?: number;
|
|
44
|
+
/** Lifecycle-serving compatibility epoch; absent pre-epoch records are epoch 1. */
|
|
45
|
+
servingEpoch?: number;
|
|
44
46
|
stoppedAt?: number;
|
|
45
47
|
}
|
|
46
48
|
export interface TelegramDaemonFs {
|
|
@@ -201,15 +203,59 @@ export declare function liveOwnershipLockDecision(input: {
|
|
|
201
203
|
attached: false;
|
|
202
204
|
provisional: true;
|
|
203
205
|
} | undefined;
|
|
206
|
+
type OwnerHeartbeatSidecar = {
|
|
207
|
+
pid: number;
|
|
208
|
+
incarnation: string;
|
|
209
|
+
ownerId: string;
|
|
210
|
+
acquisitionId: string;
|
|
211
|
+
heartbeatAt: number;
|
|
212
|
+
};
|
|
213
|
+
export interface OwnerFreshnessSnapshot {
|
|
214
|
+
ownerTag: Pick<OwnerHeartbeatSidecar, "pid" | "incarnation" | "ownerId" | "acquisitionId"> | null;
|
|
215
|
+
effectiveHeartbeatAt: number | undefined;
|
|
216
|
+
legacyEmbedded: boolean;
|
|
217
|
+
state: DaemonState | undefined;
|
|
218
|
+
}
|
|
219
|
+
export declare function readOwnerFreshnessSnapshot(input: {
|
|
220
|
+
settings: Settings;
|
|
221
|
+
fs?: TelegramDaemonFs;
|
|
222
|
+
}): Promise<OwnerFreshnessSnapshot>;
|
|
223
|
+
/** Marker-free steady heartbeat renewal. The final lock reread fences a stale writer. */
|
|
224
|
+
export declare function renewOwnerHeartbeatSidecar(input: {
|
|
225
|
+
settings: Settings;
|
|
226
|
+
ownerId: string;
|
|
227
|
+
acquisitionId?: string;
|
|
228
|
+
fs?: TelegramDaemonFs;
|
|
229
|
+
now?: () => number;
|
|
230
|
+
pid?: number;
|
|
231
|
+
pidIncarnation?: (pid: number) => string | undefined;
|
|
232
|
+
}): Promise<boolean>;
|
|
233
|
+
export interface NotificationRootRegistration {
|
|
234
|
+
root?: string;
|
|
235
|
+
managed?: boolean;
|
|
236
|
+
token?: string;
|
|
237
|
+
}
|
|
238
|
+
export declare function readNotificationRootRegistration(input: {
|
|
239
|
+
settings: Settings;
|
|
240
|
+
sessionId: string;
|
|
241
|
+
fs?: TelegramDaemonFs;
|
|
242
|
+
}): Promise<NotificationRootRegistration>;
|
|
243
|
+
export interface RegisterNotificationRootResult {
|
|
244
|
+
root: string;
|
|
245
|
+
/** Per-registration ownership token fencing a stale same-session cleanup from deleting a live replacement registration. */
|
|
246
|
+
token: string;
|
|
247
|
+
}
|
|
204
248
|
export declare function registerNotificationRoot(input: {
|
|
205
249
|
settings: Settings;
|
|
206
250
|
cwd: string;
|
|
207
251
|
sessionId: string;
|
|
208
252
|
fs?: TelegramDaemonFs;
|
|
209
|
-
}): Promise<
|
|
253
|
+
}): Promise<RegisterNotificationRootResult>;
|
|
210
254
|
export interface UnregisterNotificationRootResult {
|
|
211
255
|
root: string;
|
|
212
256
|
remainingRoots: number;
|
|
257
|
+
/** Opaque fingerprint of the exact registry written by this successful unregister. */
|
|
258
|
+
registryFingerprint?: string;
|
|
213
259
|
}
|
|
214
260
|
/** Remove one session's Telegram scan root without disturbing other live session roots. */
|
|
215
261
|
export declare function unregisterNotificationRoot(input: {
|
|
@@ -217,7 +263,16 @@ export declare function unregisterNotificationRoot(input: {
|
|
|
217
263
|
cwd: string;
|
|
218
264
|
sessionId: string;
|
|
219
265
|
fs?: TelegramDaemonFs;
|
|
266
|
+
/** Ownership token minted by this runtime's own registration; a stale cleanup presenting an older token never deletes a replacement registration. */
|
|
267
|
+
registrationToken?: string;
|
|
220
268
|
}): Promise<UnregisterNotificationRootResult>;
|
|
269
|
+
/** Run an action while the roots registry lock excludes any registration or unregister. */
|
|
270
|
+
export declare function withNotificationRootRegistryFence(input: {
|
|
271
|
+
settings: Settings;
|
|
272
|
+
registryFingerprint: string;
|
|
273
|
+
action: () => Promise<void>;
|
|
274
|
+
fs?: TelegramDaemonFs;
|
|
275
|
+
}): Promise<boolean>;
|
|
221
276
|
/**
|
|
222
277
|
* Leak artifact prefixes left when native exact-unlink cleanup is retained
|
|
223
278
|
* (#2956). Nothing previously reaped these; they accumulate in the notifications
|
|
@@ -319,6 +374,7 @@ export declare function isFreshLiveOwner(input: {
|
|
|
319
374
|
chatId: string;
|
|
320
375
|
pidAlive: (pid: number) => boolean;
|
|
321
376
|
pidIncarnation?: (pid: number) => string | undefined;
|
|
377
|
+
effectiveHeartbeatAt?: number;
|
|
322
378
|
}): boolean;
|
|
323
379
|
/** True only when a physically live matching owner can serve this build's daemon lifecycle contract. */
|
|
324
380
|
export declare function isCurrentCompatibleOwner(input: {
|
|
@@ -328,6 +384,7 @@ export declare function isCurrentCompatibleOwner(input: {
|
|
|
328
384
|
chatId: string;
|
|
329
385
|
pidAlive: (pid: number) => boolean;
|
|
330
386
|
pidIncarnation?: (pid: number) => string | undefined;
|
|
387
|
+
effectiveHeartbeatAt?: number;
|
|
331
388
|
}): boolean;
|
|
332
389
|
export declare function acquireDaemonOwnership(input: {
|
|
333
390
|
settings: Settings;
|
|
@@ -399,7 +456,7 @@ export declare function retireProvisionalDaemonOwnership(input: {
|
|
|
399
456
|
/** Only no-child confirmation may retire a ready-like launcher publication. */
|
|
400
457
|
allowReadyWithoutChildPid?: boolean;
|
|
401
458
|
}): Promise<boolean>;
|
|
402
|
-
/** Wait for a matching
|
|
459
|
+
/** Wait for a matching compatible owner to publish a ready state. Readiness is serving-epoch gated (same-epoch cross-generation owners attach, per isCurrentCompatibleOwner); generation is inventory-only here. Mutation paths still require exact current-generation equality. */
|
|
403
460
|
export declare function waitForTelegramDaemonReady(input: {
|
|
404
461
|
settings: Settings;
|
|
405
462
|
ownerId?: string;
|
|
@@ -548,6 +605,9 @@ export declare function ensureTelegramDaemonRunningDetailed(input: {
|
|
|
548
605
|
settings: Settings;
|
|
549
606
|
cwd: string;
|
|
550
607
|
sessionId: string;
|
|
608
|
+
registerRoot?: boolean;
|
|
609
|
+
/** Receives each registration this ensure operation minted so the caller can fence its own later cleanup. */
|
|
610
|
+
onRegistered?: (registration: RegisterNotificationRootResult) => void;
|
|
551
611
|
}, deps?: TelegramDaemonDeps): Promise<EnsureTelegramDaemonDetailedResult>;
|
|
552
612
|
/**
|
|
553
613
|
* Legacy compatibility mapping for callers that only distinguish ownership from
|
|
@@ -600,7 +660,10 @@ export type TelegramPollResult = {
|
|
|
600
660
|
export interface TelegramUpdatePollerOptions {
|
|
601
661
|
botApi: BotApi;
|
|
602
662
|
runtime: NotificationOperatorRuntime;
|
|
603
|
-
backoff:
|
|
663
|
+
backoff: {
|
|
664
|
+
next(): number;
|
|
665
|
+
reset(): void;
|
|
666
|
+
};
|
|
604
667
|
processUpdate: (update: unknown) => Promise<TelegramUpdateOutcome>;
|
|
605
668
|
health?: TelegramPollHealth;
|
|
606
669
|
}
|
|
@@ -785,6 +848,9 @@ export declare class TelegramNotificationDaemon {
|
|
|
785
848
|
private readonly pool;
|
|
786
849
|
private readonly poller;
|
|
787
850
|
private readonly dispatchState;
|
|
851
|
+
/** Bot-wide flood-control window; inbound polling remains eligible during it. */
|
|
852
|
+
private botCooldownUntil;
|
|
853
|
+
private warnedBotCooldownUntil;
|
|
788
854
|
/** Original markdown of rich messages we sent (chat+message_id), for restoring reply context on inbound replies. */
|
|
789
855
|
private readonly replyStore;
|
|
790
856
|
/** Per-session debounce + monotonic draft-id state for opt-in draft streaming. */
|
|
@@ -871,6 +937,9 @@ export declare class TelegramNotificationDaemon {
|
|
|
871
937
|
private refreshBotIdentity;
|
|
872
938
|
/** Map a lifecycle response/error to a user-facing message (G010 surfacing). */
|
|
873
939
|
private formatLifecycleResponse;
|
|
940
|
+
private callBotApi;
|
|
941
|
+
private readonly callBotApiClassified;
|
|
942
|
+
private botApiWithOutcomeCollector;
|
|
874
943
|
constructor(opts: TelegramDaemonOptions);
|
|
875
944
|
private createSessionRouter;
|
|
876
945
|
loadAliases(): Promise<void>;
|
|
@@ -149,6 +149,13 @@ export interface CreateAgentSessionOptions {
|
|
|
149
149
|
goalToolAllowedOps?: readonly ("create" | "get" | "complete" | "resume" | "drop" | "pause")[];
|
|
150
150
|
/** Optional per-session allowlist for tools exposed through search_tool_bm25. */
|
|
151
151
|
discoverableToolAllowedNames?: readonly string[];
|
|
152
|
+
/**
|
|
153
|
+
* Discoverable built-in tools that must stay in the initial active set even when
|
|
154
|
+
* `tools.discoveryMode === "all"` would otherwise hide them behind `search_tool_bm25`.
|
|
155
|
+
* Used for coordination tools (e.g. `irc`) that a subagent must be able to use
|
|
156
|
+
* immediately without first spending a discovery round-trip to find them.
|
|
157
|
+
*/
|
|
158
|
+
alwaysActiveToolNames?: readonly string[];
|
|
152
159
|
/** Optional shared agent registry for IRC routing. Default: AgentRegistry.global(). */
|
|
153
160
|
agentRegistry?: AgentRegistry;
|
|
154
161
|
/** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
|
|
@@ -677,6 +677,8 @@ export declare class AgentSession {
|
|
|
677
677
|
* Get a tool by name from the registry.
|
|
678
678
|
*/
|
|
679
679
|
getToolByName(name: string): AgentTool | undefined;
|
|
680
|
+
/** Get a registered tool with the same guards used for model-facing execution. */
|
|
681
|
+
getToolForExecution(name: string): AgentTool | undefined;
|
|
680
682
|
/**
|
|
681
683
|
* Register a UI/control-plane request handler for a currently foregrounded
|
|
682
684
|
* managed bash execution. This is intentionally narrower than generic
|
|
@@ -129,7 +129,7 @@ export declare function matchesMigrationArtifactRoot(pathname: string, identity:
|
|
|
129
129
|
}, expectedTree: NativeDirectoryTreeSnapshot, platform?: NodeJS.Platform): boolean;
|
|
130
130
|
export declare function restorePreparedArtifactRoot(scope: ManagedScope, source: ManagedCandidate, lock?: ManagedStorageLock): void;
|
|
131
131
|
/** Resume tombstoned cleanup under its original operation lease without restoring retired candidates. */
|
|
132
|
-
export declare function reconcileManagedTombstones(scope: ManagedScope): Promise<void>;
|
|
132
|
+
export declare function reconcileManagedTombstones(scope: ManagedScope, expectedCandidate?: ManagedCandidate): Promise<void>;
|
|
133
133
|
/** Create the v2 binding and private write protocol directories before managed writes. */
|
|
134
134
|
export declare function prepareManagedSessionScopeForWrite(scope: ManagedScope, policy?: ManagedSessionSecurityPolicy, authority?: ManagedCandidateWriteAuthority, expectedCandidate?: ManagedCandidate, expectedIdentity?: ResumeSessionIdentity): Promise<ManagedScopeResolution>;
|
|
135
135
|
/**
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentProgress, TaskToolDetails } from "./types";
|
|
2
|
+
export type ProviderRetryKind = NonNullable<AgentProgress["retryState"]>["kind"];
|
|
3
|
+
export declare function classifyProviderRetry(errorMessage: string): ProviderRetryKind;
|
|
4
|
+
export declare function providerNameFromModel(model: string | undefined): string | undefined;
|
|
5
|
+
export declare function providerRetryPhaseLabel(kind: ProviderRetryKind): string;
|
|
6
|
+
export declare function providerProgressAgeLabel(retryState: NonNullable<AgentProgress["retryState"]>, nowMs: number): string;
|
|
7
|
+
export declare function hasActiveProviderRetryInTaskDetails(details: TaskToolDetails, seen?: WeakSet<object>): boolean;
|
|
8
|
+
export declare function hasActiveProviderRetryInProgress(progress: AgentProgress, seen?: WeakSet<object>): boolean;
|
|
9
|
+
export interface ProviderDegradationGroup {
|
|
10
|
+
provider: string;
|
|
11
|
+
count: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function collectProviderDegradationGroups(progress: readonly AgentProgress[]): ProviderDegradationGroup[];
|
|
@@ -416,6 +416,9 @@ export interface AgentProgress {
|
|
|
416
416
|
attempt: number;
|
|
417
417
|
maxAttempts: number;
|
|
418
418
|
unbounded?: boolean;
|
|
419
|
+
kind: "first_event_timeout" | "idle_stream_stall" | "provider_error";
|
|
420
|
+
provider?: string;
|
|
421
|
+
lastProviderProgressAtMs?: number;
|
|
419
422
|
delayMs: number;
|
|
420
423
|
errorMessage: string;
|
|
421
424
|
startedAtMs: number;
|
|
@@ -198,9 +198,11 @@ export interface ToolSession {
|
|
|
198
198
|
/** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
|
|
199
199
|
getAgentId?: () => string | null;
|
|
200
200
|
/** Look up a registered tool by name (used by the eval js backend's tool bridge). */
|
|
201
|
+
getToolByName?: (name: string) => AgentTool | undefined;
|
|
202
|
+
/** Look up a registered tool with the session's execution guards applied. */
|
|
203
|
+
getToolForExecution?: (name: string) => AgentTool | undefined;
|
|
201
204
|
/** Purge undelivered queued custom messages matching the predicate. Returns counts. */
|
|
202
205
|
purgeQueuedCustomMessages?: (predicate: (message: CustomMessage) => boolean) => PurgeQueuedCustomMessagesResult;
|
|
203
|
-
getToolByName?: (name: string) => AgentTool | undefined;
|
|
204
206
|
/** Agent registry for IRC routing across live sessions. */
|
|
205
207
|
agentRegistry?: AgentRegistry;
|
|
206
208
|
/** Optional restricted bash command prefixes for read-only role agents and constrained modes. */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.11.
|
|
4
|
+
"version": "0.11.10",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gajae-code.com",
|
|
7
7
|
"author": "Yeachan-Heo and Gajae Code Contributors",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"@agentclientprotocol/sdk": "1.2.1",
|
|
54
54
|
"@babel/parser": "^7.29.3",
|
|
55
55
|
"@mozilla/readability": "^0.6.0",
|
|
56
|
-
"@gajae-code/stats": "0.11.
|
|
57
|
-
"@gajae-code/agent-core": "0.11.
|
|
58
|
-
"@gajae-code/ai": "0.11.
|
|
59
|
-
"@gajae-code/bridge-client": "0.11.
|
|
60
|
-
"@gajae-code/natives": "0.11.
|
|
61
|
-
"@gajae-code/tui": "0.11.
|
|
62
|
-
"@gajae-code/utils": "0.11.
|
|
56
|
+
"@gajae-code/stats": "0.11.10",
|
|
57
|
+
"@gajae-code/agent-core": "0.11.10",
|
|
58
|
+
"@gajae-code/ai": "0.11.10",
|
|
59
|
+
"@gajae-code/bridge-client": "0.11.10",
|
|
60
|
+
"@gajae-code/natives": "0.11.10",
|
|
61
|
+
"@gajae-code/tui": "0.11.10",
|
|
62
|
+
"@gajae-code/utils": "0.11.10",
|
|
63
63
|
"@puppeteer/browsers": "^2.13.0",
|
|
64
64
|
"@types/turndown": "5.0.6",
|
|
65
65
|
"@xterm/headless": "^6.0.0",
|
|
@@ -70,6 +70,7 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
|
|
|
70
70
|
"agent_session:getAsyncDeliveryStateForAcp":
|
|
71
71
|
"internal ACP lifecycle quiescence plumbing, not a user-facing control seam",
|
|
72
72
|
"agent_session:getToolByName": "internal accessor/plumbing, not a user-facing control seam",
|
|
73
|
+
"agent_session:getToolForExecution": "internal accessor/plumbing, not a user-facing control seam",
|
|
73
74
|
"agent_session:registerForegroundBashBackgroundRequestHandler":
|
|
74
75
|
"internal accessor/plumbing, not a user-facing control seam",
|
|
75
76
|
"agent_session:hasForegroundBashBackgroundRequestHandler":
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -413,6 +413,7 @@ async function runTelegramSetup(cmd: NotifyCommandArgs, deps: NotifyCommandDeps)
|
|
|
413
413
|
settings,
|
|
414
414
|
cwd: process.cwd(),
|
|
415
415
|
sessionId: `notify-cli-${process.pid}`,
|
|
416
|
+
registerRoot: false,
|
|
416
417
|
}),
|
|
417
418
|
persistInactive: async marker => await persistTelegramActivationMarker(settings, marker),
|
|
418
419
|
clearInactive: async marker => await clearTelegramActivationMarker(settings, marker),
|
|
@@ -100,11 +100,11 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
100
100
|
architect: "opencode-go/deepseek-v4-pro",
|
|
101
101
|
}),
|
|
102
102
|
profile("claude-opus", ["anthropic"], {
|
|
103
|
-
default: "anthropic/claude-opus-
|
|
103
|
+
default: "anthropic/claude-opus-5:xhigh",
|
|
104
104
|
executor: "anthropic/claude-sonnet-5",
|
|
105
|
-
planner: "anthropic/claude-opus-
|
|
106
|
-
critic: "anthropic/claude-opus-
|
|
107
|
-
architect: "anthropic/claude-opus-
|
|
105
|
+
planner: "anthropic/claude-opus-5:low",
|
|
106
|
+
critic: "anthropic/claude-opus-5:high",
|
|
107
|
+
architect: "anthropic/claude-opus-5:xhigh",
|
|
108
108
|
}),
|
|
109
109
|
profile("claude-fable", ["anthropic"], {
|
|
110
110
|
default: "anthropic/claude-fable-5:xhigh",
|
|
@@ -271,7 +271,7 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
271
271
|
critic: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
272
272
|
}),
|
|
273
273
|
profile("opus-codex", ["anthropic", "openai-codex"], {
|
|
274
|
-
default: "anthropic/claude-opus-
|
|
274
|
+
default: "anthropic/claude-opus-5:xhigh",
|
|
275
275
|
executor: "openai-codex/gpt-5.6-terra:low",
|
|
276
276
|
planner: "anthropic/claude-sonnet-5",
|
|
277
277
|
critic: "openai-codex/gpt-5.6-sol:xhigh",
|
|
@@ -287,8 +287,8 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
287
287
|
profile("fable-opus-codex", ["anthropic", "openai-codex"], {
|
|
288
288
|
default: "anthropic/claude-fable-5:high",
|
|
289
289
|
executor: "openai-codex/gpt-5.6-terra:medium",
|
|
290
|
-
planner: "anthropic/claude-opus-
|
|
291
|
-
critic: "anthropic/claude-opus-
|
|
290
|
+
planner: "anthropic/claude-opus-5:medium",
|
|
291
|
+
critic: "anthropic/claude-opus-5:high",
|
|
292
292
|
architect: "openai-codex/gpt-5.6-sol:xhigh",
|
|
293
293
|
}),
|
|
294
294
|
];
|
|
@@ -2564,13 +2564,14 @@ export class ModelRegistry {
|
|
|
2564
2564
|
async getApiKey(
|
|
2565
2565
|
model: Model<Api>,
|
|
2566
2566
|
sessionId?: string,
|
|
2567
|
-
options: { credentialSelector?: AuthCredentialSelector } = {},
|
|
2567
|
+
options: { credentialSelector?: AuthCredentialSelector; signal?: AbortSignal } = {},
|
|
2568
2568
|
): Promise<string | undefined> {
|
|
2569
2569
|
return this.#getApiKeyOrNoAuth(model.provider, () =>
|
|
2570
2570
|
this.authStorage.getApiKey(model.provider, sessionId, {
|
|
2571
2571
|
baseUrl: model.baseUrl,
|
|
2572
2572
|
modelId: model.id,
|
|
2573
2573
|
credentialSelector: options.credentialSelector,
|
|
2574
|
+
signal: options.signal,
|
|
2574
2575
|
}),
|
|
2575
2576
|
);
|
|
2576
2577
|
}
|
|
@@ -2582,12 +2583,13 @@ export class ModelRegistry {
|
|
|
2582
2583
|
provider: string,
|
|
2583
2584
|
sessionId?: string,
|
|
2584
2585
|
baseUrl?: string,
|
|
2585
|
-
options: { credentialSelector?: AuthCredentialSelector } = {},
|
|
2586
|
+
options: { credentialSelector?: AuthCredentialSelector; signal?: AbortSignal } = {},
|
|
2586
2587
|
): Promise<string | undefined> {
|
|
2587
2588
|
return this.#getApiKeyOrNoAuth(provider, () =>
|
|
2588
2589
|
this.authStorage.getApiKey(provider, sessionId, {
|
|
2589
2590
|
baseUrl,
|
|
2590
2591
|
credentialSelector: options.credentialSelector,
|
|
2592
|
+
signal: options.signal,
|
|
2591
2593
|
}),
|
|
2592
2594
|
);
|
|
2593
2595
|
}
|
|
@@ -30,7 +30,7 @@ function toolResultHasError(result: AgentToolResult): boolean {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function getTool(session: ToolSession, name: string): AgentTool {
|
|
33
|
-
const tool = session.getToolByName?.(name);
|
|
33
|
+
const tool = session.getToolForExecution ? session.getToolForExecution(name) : session.getToolByName?.(name);
|
|
34
34
|
if (!tool) {
|
|
35
35
|
throw new ToolError(`Unknown tool from js runtime: ${name}`);
|
|
36
36
|
}
|
|
@@ -17,6 +17,7 @@ import { type DeepInterviewRepairResult, runDeepInterviewRepairCommand } from ".
|
|
|
17
17
|
import { canonicalDeepInterviewJson, validateDeepInterviewV1Envelope } from "./deep-interview-state";
|
|
18
18
|
import { modeStatePath } from "./session-layout";
|
|
19
19
|
import {
|
|
20
|
+
isNativeDeepInterviewV1,
|
|
20
21
|
readExistingStateForMutation,
|
|
21
22
|
transformGuardedWorkflowEnvelopeAtomic,
|
|
22
23
|
verifyWorkflowEnvelopeReceiptValue,
|
|
@@ -295,7 +296,7 @@ async function revision(cwd: string, session: string): Promise<{ revision: numbe
|
|
|
295
296
|
if (receipt === "receipt-missing") throw new Error("DI_RECEIPT_MISSING");
|
|
296
297
|
if (receipt === "checksum-mismatch") throw new Error("DI_RECEIPT_CHECKSUM_MISMATCH");
|
|
297
298
|
try {
|
|
298
|
-
if (
|
|
299
|
+
if (isNativeDeepInterviewV1(observed.value)) validateDeepInterviewV1Envelope(observed.value);
|
|
299
300
|
} catch {
|
|
300
301
|
throw new Error("DI_STATE_SCHEMA_INVALID");
|
|
301
302
|
}
|
|
@@ -101,6 +101,21 @@ export interface TransitionValidationResult {
|
|
|
101
101
|
// Pure helpers: records
|
|
102
102
|
// =============================================================================
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* `deepInterview.dimension` is agent-supplied free text on post-topology asks, but the persisted
|
|
106
|
+
* envelope only accepts canonical dimension ids. Fold obvious case/label variants down, and drop
|
|
107
|
+
* anything unrecognized rather than persisting a value the validator rejects.
|
|
108
|
+
*/
|
|
109
|
+
function canonicalDimension(value: string | undefined): string | undefined {
|
|
110
|
+
if (value === undefined) return undefined;
|
|
111
|
+
const normalized = value.trim().toLowerCase();
|
|
112
|
+
if (normalized === "topology") return "topology";
|
|
113
|
+
for (const dimension of ["goal", "constraints", "criteria", "context"]) {
|
|
114
|
+
if (normalized === dimension || normalized.startsWith(`${dimension} `)) return dimension;
|
|
115
|
+
}
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
export function buildAnswerShell(
|
|
105
120
|
input: DeepInterviewAnswerInput,
|
|
106
121
|
now: string = new Date().toISOString(),
|
|
@@ -116,7 +131,7 @@ export function buildAnswerShell(
|
|
|
116
131
|
selected_options: input.selectedOptions,
|
|
117
132
|
custom_input: input.customInput,
|
|
118
133
|
component: input.component,
|
|
119
|
-
dimension: input.dimension,
|
|
134
|
+
dimension: canonicalDimension(input.dimension),
|
|
120
135
|
ambiguity_at_ask: input.ambiguity,
|
|
121
136
|
lifecycle: "answered",
|
|
122
137
|
answered_at: now,
|
|
@@ -298,7 +298,7 @@ export function workflowEnvelopeContentSha256(value: unknown): string {
|
|
|
298
298
|
.update(JSON.stringify(canonicalizeJson(withoutReceiptChecksum(value))))
|
|
299
299
|
.digest("hex");
|
|
300
300
|
}
|
|
301
|
-
function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean {
|
|
301
|
+
export function isNativeDeepInterviewV1(value: Record<string, unknown>): boolean {
|
|
302
302
|
if (value.skill !== "deep-interview") return false;
|
|
303
303
|
return value.schema_version === 1 || (isPlainObject(value.state) && value.state.schema_version === 1);
|
|
304
304
|
}
|