@gajae-code/coding-agent 0.12.8 → 0.12.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/types/cli/notify-cli.d.ts +1 -0
  3. package/dist/types/commands/sdk.d.ts +23 -0
  4. package/dist/types/discovery/agents-md.d.ts +11 -1
  5. package/dist/types/modes/types.d.ts +6 -0
  6. package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -1
  7. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  8. package/dist/types/sdk/bus/notification-service.d.ts +23 -0
  9. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -3
  10. package/dist/types/session/internal/managed-session-storage.d.ts +20 -2
  11. package/dist/types/session/internal/native-publish-outcome.d.ts +2 -2
  12. package/dist/types/session/session-manager.d.ts +17 -0
  13. package/dist/types/session/session-storage.d.ts +1 -0
  14. package/dist/types/system-prompt.d.ts +12 -1
  15. package/dist/types/tools/composer-bash-policy.d.ts +9 -1
  16. package/dist/types/tools/computer.d.ts +35 -2
  17. package/dist/types/tools/todo-write.d.ts +2 -0
  18. package/package.json +8 -8
  19. package/scripts/verify-gjc-sdk-canonicalization.ts +14 -1
  20. package/src/cli/notify-cli.ts +12 -6
  21. package/src/commands/sdk.ts +54 -0
  22. package/src/config/model-profiles.ts +8 -0
  23. package/src/config/model-registry.ts +4 -1
  24. package/src/discovery/agents-md.ts +76 -19
  25. package/src/gjc-runtime/psmux-detect.ts +12 -16
  26. package/src/gjc-runtime/tmux-sessions.ts +8 -1
  27. package/src/internal-urls/docs-index.generated.ts +6 -5
  28. package/src/modes/components/extensions/inspector-panel.ts +9 -5
  29. package/src/modes/controllers/selector-controller.ts +61 -43
  30. package/src/modes/print-mode.ts +3 -0
  31. package/src/modes/types.ts +6 -0
  32. package/src/prompts/system/system-prompt.md +1 -0
  33. package/src/prompts/tools/computer.md +6 -1
  34. package/src/sdk/bus/chat-daemon-control.ts +5 -3
  35. package/src/sdk/bus/daemon-paths.ts +2 -0
  36. package/src/sdk/bus/notification-service.ts +140 -7
  37. package/src/sdk/bus/telegram-daemon-contract.ts +4 -3
  38. package/src/sdk/session.ts +24 -7
  39. package/src/session/agent-session.ts +176 -34
  40. package/src/session/internal/managed-session-storage.ts +280 -248
  41. package/src/session/internal/native-publish-outcome.ts +12 -2
  42. package/src/session/session-manager.ts +140 -6
  43. package/src/session/session-storage.ts +3 -0
  44. package/src/slash-commands/builtin-registry.ts +35 -11
  45. package/src/system-prompt.ts +28 -15
  46. package/src/tools/bash.ts +3 -1
  47. package/src/tools/composer-bash-policy.ts +21 -5
  48. package/src/tools/computer.ts +322 -45
  49. package/src/tools/todo-write.ts +29 -11
  50. package/src/utils/command-args.ts +19 -30
  51. package/src/utils/lang-from-path.ts +2 -4
package/CHANGELOG.md CHANGED
@@ -2,10 +2,41 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.12.11] - 2026-08-03
6
+
7
+ ### Fixed
8
+
9
+ - Side-effecting native macOS computer input now restores the global cursor after releasing held input on success, cancellation, supervisor rejection, and action failure. Batches containing input execute in one serialized native capture-to-restore transaction, while screenshot/wait-only operations remain cursor-neutral; capture/restore failures are reported distinctly without masking the primary action error, and global focus behavior remains unchanged (#3642, #3781).
10
+ - Managed session rewrites and authority-absent Darwin appends now use native identity-verified atomic replacement instead of deterministically failing with `managed_replace_exact_unavailable` or risking a torn JSONL tail; uncertain readable outcomes are re-fsynced and carried through ctime-bound strict adoption before recovery reports them durable. This unblocks Darwin compaction and session append durability when retained native authority is absent (#3742, #3760).
11
+ - `todo_write` recovery reminders now distinguish rejected payloads from runtime aborts, preserve the available cause, and require durable state reconciliation instead of incorrectly telling the agent to change a valid payload (#3743, #3760).
12
+ - Authority-absent Darwin `appendSync` regression coverage now exercises the replace-based race window (destination mutation during successor staging) instead of the retired in-place `O_APPEND` open path, and documents that ctime-only destination transitions are tolerated by exact replacement.
13
+
14
+ ## [0.12.10] - 2026-08-03
15
+
16
+ ### Added
17
+
18
+ - `/login anthropic --manual` pairs by pasting the authorization code Anthropic shows in the browser instead of waiting on a `localhost:54545` callback the browser cannot reach. Complete it with `/login <code>`. Use it when gjc runs over SSH, in a container, or on a headless host; the default `/login anthropic` still uses the loopback callback. The flag is resolved before the paste fallback, so it is never mistaken for an authorization code, and providers without a paste-a-code redirect reject it explicitly instead of silently falling back.
19
+
20
+ ### Fixed
21
+
22
+ - Composer Bash policy rejections now identify the active provider surface and direct Cursor Composer models to their native repository tools, enabling the agent runtime's bounded automatic recovery instead of leaving a blocked shell attempt as a terminal turn.
23
+ - The `AgentSession retry fallback > invalidates an auth-failed managed credential` test now uses a stored credential instead of a runtime-key override, matching the pin-guard behavior added in #3724 where `--api-key`/`--credential` pinned keys are never invalidated; the shared test fixture installed runtime keys as plumbing, which silently tripped the new guard and blocked the auth invalidation path.
24
+ - The Extension Control Center inspector no longer crashes when a narrow two-column layout leaves its preview pane fewer than two columns wide.
25
+ - Prompt-template positional arguments now preserve literal `$@` and `$ARGUMENTS` text instead of recursively expanding it during placeholder substitution.
26
+ - Native Windows session and GC commands now report the searched `psmux` / `pmux` / `tmux` provider set when no compatible multiplexer is available instead of leaking a literal `tmux` spawn error (#3688).
27
+ - Managed-session recovery now preserves committed mutation state and the actual Linux fallback primitive in native publish receipts, preventing unsafe retry classification after a post-link staging unlink failure (#3746).
28
+ - The system prompt now requires non-ASCII tool-input text to be written as literal UTF-8 rather than hand-spelled `\uXXXX` escapes, including JSON serialized into a string field, while leaving escapes that are intended source syntax alone. Models that hand-spell hex codepoints for CJK mis-type them, and each mis-typed escape decodes to a valid-but-wrong syllable, so Korean text in tool parameters silently arrives corrupted (anthropics/claude-code#83033).
29
+ - The `models.yml` validation error for a custom provider without a credential source now explains that `auth` selects only the auth scheme and lists the three corrective forms (`apiKeyEnv`, literal `apiKey`, or `auth: none`) instead of restating the rule that was already misread. `docs/models.md` documents the same contract in a table (#3738).
30
+ - Credential rotation no longer mutates a pinned credential, and no longer reports a rotation that did not happen. `#markFailedCredential` now applies its pin guard first and for every trigger class, consulting both the `--api-key` runtime override and the `--credential` runtime selector — previously the guard existed only on the quota path and checked only the API-key override, so a `--credential` pin could be rotated away from and the `auth` path could invalidate a pinned credential outright. Both paths now also require the re-resolved credential to actually differ before reporting a rotation: `invalidateCredentialMatching` reports that a row was matched and blocked, which is not the same as the session having moved to a different credential, and with a single-row pool it was true while nothing rotated. A terminal `forbidden` failure is now excluded from retry admission and makes no credential-state change at all. Finally, a rotation is only converted into a same-model retry when the fallback controller could actually be rewound; `restorePreviousEntryForRetry()` refuses once an entry's restore budget is spent, and ignoring that refusal left `activeIndex` on the next entry while the session still requested the previous model.
31
+ - Dead-owner notification recovery now preserves a machine-readable transition block, marker-age diagnostics, and safe force-recovery guidance without weakening ownership proofs (#3762).
32
+ - Detached SDK session hosts no longer outlive the broker that spawned them. A host whose broker died without teardown (crash, `SIGKILL`, restart without `--close-session-hosts`) previously stayed resident forever, holding its session's memory — hundreds of MB per orphan. Each host now polls the broker discovery publication and, after a bounded grace period with no live broker, disposes itself through the same graceful teardown a `SIGTERM` takes. A replacement broker resets the window, so hosts still survive ordinary broker restarts, and a transient discovery read failure is treated as ambiguity rather than proof of orphanhood.
33
+ - Syntax highlighting now recognizes special filenames such as `CMakeLists.txt`, `Dockerfile.*`, `Makefile`, and `.env.*` before generic filename extensions.
34
+
5
35
  ## [0.12.8] - 2026-08-02
6
36
  ### Added
7
37
 
8
38
  - Added the paginated public SDK query `providers.list/active` (Q29), returning deterministic, deduplicated `{ provider, connectionKind }` descriptors for locally eligible providers without exposing credentials or performing remote health probes.
39
+ - Added the opt-in Alibaba Token Plan Pro profile with `deepseek-v4-flash-0731:max` for execution and `glm-5.2:xhigh` for independent criticism, preserving the existing Balanced profile unchanged.
9
40
 
10
41
  ### Added
11
42
 
@@ -13,10 +44,14 @@
13
44
 
14
45
  ### Fixed
15
46
 
47
+ - Windows automatic tmux resolution now selects `psmux` then `pmux` by canonical command order without rejecting distinct lower-priority aliases; it probes `tmux` only when neither named provider is available (#3725).
48
+ - CI failure extraction now aggregates Bun failure and suite-error summaries across every test invocation in a job log instead of silently using only the first summary.
16
49
  - Ordinary `ask` selectors now bound long question premises and page through every premise row without skipping rows hidden by overflow indicators (#3675).
17
50
  - First-event timeout retries now require a typed, content-free failure from the current clean attempt scope, preventing prior or stale extension activity from suppressing or admitting a later request (#3553).
18
51
  - The issue-1979 Korean prose wrap test now cleans up inherited multiplexer env vars (`TMUX`, `TMUX_PANE`, etc.) so it deterministically exercises the plain-terminal render path regardless of the CI runner's terminal session (#1979).
19
52
  - The model selector's assignment menu now shows the model each role currently resolves to (`Set as EXECUTOR (Executor) — now: anthropic/claude-haiku-4-5`), distinguishing an unset default, a role that inherits the default, and a configured-but-unresolvable selector. Previously the role rows were unlabeled, so the only way to learn a role's model was to scan the whole 800+ entry model list for role badges.
53
+ - Standalone `AGENTS.md` ancestor discovery now bounds directory traversal, per-file reads, and aggregate instruction bytes while surfacing content-free omission warnings (#3722).
54
+
20
55
  ## [0.12.7] - 2026-07-31
21
56
 
22
57
  ## [0.12.6] - 2026-07-31
@@ -26,6 +61,7 @@
26
61
 
27
62
  ### Fixed
28
63
 
64
+ - Managed session publication now works on filesystems that implement no `renameat2` rename flags at all. NFS rejects both `RENAME_NOREPLACE` and `RENAME_EXCHANGE` with `EINVAL`, so publishing a migration receipt failed the whole resume with `Could not open managed session: invalid_request`. The no-replace publish now falls back to `linkat(2)`, which fails with `EEXIST` on an occupied destination and therefore carries the identical no-overwrite guarantee; the fallback is authorized only by a pre-mutation missing-primitive outcome, so a publish that may have committed is never retried under a second primitive. The staged descriptor is retained across publication and the staging link is removed only after it is released, because unlinking a still-open name on NFS silly-renames it and would leave a second link on the published inode.
29
65
  - Settings now requests a repaint after asynchronous GJC bundle and plugin views rebuild, so loaded content and mutation results appear without an extra keypress (#3643).
30
66
  - `todo_write` now rejects unsupported operation keys and treats a bare `done` or `drop` as an error instead of completing or abandoning every task (#3640).
31
67
  - Deferred `agent_end` publication again settles public session readiness before slow extension handlers finish, while retaining exact cancellation leases through queued extension delivery and draining that delivery before session shutdown.
@@ -21,6 +21,7 @@ export interface NotifyCommandArgs {
21
21
  slackChannelId?: string;
22
22
  slackAuthorizedUserId?: string;
23
23
  redact?: boolean;
24
+ forceDaemonLock?: boolean;
24
25
  probe?: boolean;
25
26
  message?: string;
26
27
  }
@@ -3,6 +3,29 @@ import type { Args as ParsedArgs } from "../cli/args";
3
3
  import { type SessionLifecycleLaunchRequest } from "../sdk/broker/lifecycle";
4
4
  import { SessionManager } from "../session/session-manager";
5
5
  export declare function lifecycleArgs(request: SessionLifecycleLaunchRequest, cwd: string, agentDir: string): Promise<ParsedArgs>;
6
+ /**
7
+ * How long a session host tolerates the complete absence of a live broker
8
+ * publication before treating itself as orphaned. Hosts intentionally survive
9
+ * broker restarts (a replacement broker republishes discovery within seconds),
10
+ * so this must comfortably exceed a restart window while still bounding the
11
+ * lifetime of hosts whose broker is gone for good — otherwise every crashed or
12
+ * torn-down broker leaks a detached multi-hundred-megabyte host forever.
13
+ */
14
+ export declare const SESSION_HOST_BROKER_ABSENCE_GRACE_MS: number;
15
+ /**
16
+ * Resolves only once no live broker publication has been observable in
17
+ * `agentDir` for the full grace window. A reappearing broker (including a
18
+ * replacement with a different pid) resets the window; an unreadable
19
+ * publication is not proof of orphanhood but accrues against the same bound.
20
+ */
21
+ export declare function watchSessionHostBrokerLiveness(deps: {
22
+ agentDir: string;
23
+ now?: () => number;
24
+ sleep?: (ms: number) => Promise<void>;
25
+ readDiscovery?: (agentDir: string) => Promise<unknown>;
26
+ graceMs?: number;
27
+ pollMs?: number;
28
+ }): Promise<void>;
6
29
  /** Opens lifecycle-authorized history without letting replacement content reach readiness. */
7
30
  export declare function openLifecycleSessionManager(request: SessionLifecycleLaunchRequest, cwd: string, agentDir: string): Promise<{
8
31
  parsed: ParsedArgs;
@@ -1 +1,11 @@
1
- export {};
1
+ import { type ContextFile } from "../capability/context-file";
2
+ import type { LoadContext, LoadResult } from "../capability/types";
3
+ export type AgentsMdReader = (filePath: string, maxBytes: number) => Promise<{
4
+ content: string | null;
5
+ byteLength: number;
6
+ tooLarge: boolean;
7
+ }>;
8
+ /**
9
+ * Load standalone AGENTS.md files.
10
+ */
11
+ export declare function loadAgentsMd(ctx: LoadContext, readCandidate?: AgentsMdReader): Promise<LoadResult<ContextFile>>;
@@ -374,5 +374,11 @@ export interface OAuthSelectorOptions {
374
374
  allowExternalCredentialDiscovery?: boolean;
375
375
  trigger?: "bare-login";
376
376
  externalCredentialDiscover?: CredentialAutoImportOptions["discover"];
377
+ /**
378
+ * Pair by pasting the code the provider displays instead of waiting on the
379
+ * loopback callback. Set by `/login <provider> --manual` for browsers that
380
+ * cannot reach this machine.
381
+ */
382
+ manualCode?: boolean;
377
383
  }
378
384
  export {};
@@ -30,7 +30,9 @@ export type ChatDaemonAction = "stop" | "reload";
30
30
  * semantics; discord generation 23 / slack generation 22 hardens exact Bash
31
31
  * process-tree ownership shared by chat daemon cleanup.
32
32
  * Discord generation 24 / slack generation 23 apply provider-completeness and
33
- * effective-enable admission to chat daemon lifecycle controls.
33
+ * effective-enable admission to chat daemon lifecycle controls. Discord
34
+ * generation 25 / slack generation 24 apply identity-bound exact replacement
35
+ * cleanup shared by managed-session and daemon filesystem authority.
34
36
  */
35
37
  export declare const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>>;
36
38
  export declare function chatDaemonGeneration(kind: ChatDaemonKind): number;
@@ -13,6 +13,7 @@ export interface DaemonPaths {
13
13
  ownership: string;
14
14
  roots: string;
15
15
  steal: string;
16
+ diagnostic: string;
16
17
  aliases: string;
17
18
  seenUpdates: string;
18
19
  }
@@ -9,6 +9,21 @@ import type { SlackDiagnosticProvider } from "./slack-provider";
9
9
  * can never leak a credential regardless of where the string originated.
10
10
  */
11
11
  export declare function sanitizeDiagnostic(text: string, token?: string): string;
12
+ export interface NotificationDiagnosticEvent {
13
+ timestamp: string;
14
+ operation: string;
15
+ phase: string;
16
+ outcome: string;
17
+ reason: string;
18
+ pid?: number;
19
+ incarnation?: string;
20
+ ageMs?: number;
21
+ detail?: string;
22
+ }
23
+ /** Append a bounded, private, secret-safe daemon diagnostic event. */
24
+ export declare function writeNotificationDiagnostic(settings: Pick<Settings, "getAgentDir">, event: Omit<NotificationDiagnosticEvent, "timestamp"> & {
25
+ timestamp?: string;
26
+ }): Promise<void>;
12
27
  /** Identity evidence required to remove precisely the endpoint that was inspected. */
13
28
  export interface NotificationEndpointFileIdentity {
14
29
  dev: bigint;
@@ -54,6 +69,7 @@ export interface NotificationServiceDeps {
54
69
  fs?: NotificationServiceFs;
55
70
  now?: () => number;
56
71
  pidAlive?: (pid: number) => boolean;
72
+ pidIncarnation?: (pid: number) => string | undefined;
57
73
  fetchImpl?: typeof fetch;
58
74
  apiBase?: string;
59
75
  createDiscordDiagnostic?: (config: {
@@ -223,6 +239,9 @@ export interface NotificationRecoveryReport {
223
239
  action: DaemonRecoveryAction;
224
240
  detail: string;
225
241
  ownerId: string | undefined;
242
+ blockingReason?: string;
243
+ markerAgeMs?: number;
244
+ forceCommand?: string;
226
245
  pid: number | undefined;
227
246
  };
228
247
  }
@@ -230,6 +249,7 @@ export interface RecoveryOptions {
230
249
  settings: Settings;
231
250
  stateRoot?: string;
232
251
  deps?: NotificationServiceDeps;
252
+ forceDaemonLock?: boolean;
233
253
  }
234
254
  export interface DaemonTransitionLock {
235
255
  pid: number;
@@ -243,6 +263,9 @@ type TransitionMarkerFs = {
243
263
  writeFile?(file: string, data: string, opts?: WriteFileOptions): Promise<void>;
244
264
  readEndpointFile?(file: string): Promise<NotificationEndpointFile>;
245
265
  exactUnlink?(file: string, identity: NotificationEndpointFileIdentity): Promise<NotificationExactUnlinkResult>;
266
+ stat?(file: string): Promise<{
267
+ mtimeMs: number;
268
+ }>;
246
269
  };
247
270
  /** True only while the exact transition acquisition still occupies the marker path. */
248
271
  export declare function daemonTransitionLockIsHeld(input: {
@@ -53,10 +53,11 @@ export declare const NOTIFICATION_PROTOCOL_VERSION = 3;
53
53
  * ownership, settlement, and descendant cleanup authority. Generation 38 also
54
54
  * adds durable provider-intent admission without changing owner, reclaim,
55
55
  * signal, or spawn authority.
56
- * Generation 41 applies first-class provider-settings admission to Telegram
57
- * lifecycle controls.
56
+ * Generation 42 applies first-class provider-settings admission to Telegram
57
+ * lifecycle controls. Generation 43 applies identity-bound exact replacement
58
+ * cleanup shared by managed-session and daemon filesystem authority.
58
59
  */
59
- export declare const DAEMON_GENERATION = 41;
60
+ export declare const DAEMON_GENERATION = 43;
60
61
  /**
61
62
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
62
63
  * all builds published before this field existed; epoch 2 covered generation 29;
@@ -1,4 +1,4 @@
1
- import { type NativeDirectoryTreeSnapshot, type NativeOwnerOnlySecurityResult, type RecoveryFsRoot } from "@gajae-code/natives";
1
+ import { type NativeDirectoryTreeSnapshot, type NativeExactUnlinkResult, type NativeOwnerOnlySecurityResult, type RecoveryFsRoot } from "@gajae-code/natives";
2
2
  import { type NativePublishOutcome } from "./native-publish-outcome";
3
3
  export declare const MANAGED_ARTIFACT_MAX_DEPTH = 32;
4
4
  export declare const MANAGED_ARTIFACT_MAX_FILES = 50000;
@@ -10,8 +10,26 @@ export declare class ManagedPublishError extends Error {
10
10
  readonly diagnostic: string;
11
11
  constructor(classification: ManagedPublishError["classification"], outcome: NativePublishOutcome);
12
12
  }
13
+ export declare class ManagedReplaceError extends Error {
14
+ readonly code: string;
15
+ readonly cleanupReceiptPath: string;
16
+ readonly detachedPath: string | undefined;
17
+ readonly retainedSuccessorPath: string | undefined;
18
+ readonly retainedPlaceholderPath: string | undefined;
19
+ readonly retainedUnknownPath: string | undefined;
20
+ readonly replacementCause: unknown;
21
+ constructor(result: NativeExactUnlinkResult, cleanupReceiptPath: string, replacementCause?: unknown);
22
+ }
13
23
  /** Cleanup is safe only when the native move reports that it did not commit. */
14
24
  export declare function mayCleanManagedTreeStaging(error: unknown): boolean;
25
+ /**
26
+ * A filesystem that implements no `renameat2` rename flag at all rejects the no-replace publish
27
+ * before mutating anything: NFS answers `EINVAL` (`invalid_request`) and kernels older than 3.15
28
+ * answer `ENOSYS` (`atomic_unavailable`). Only those two pre-mutation outcomes authorize the
29
+ * `linkat` stand-in; every other failure describes a publish that was actually attempted, and
30
+ * retrying it under a different primitive could publish twice.
31
+ */
32
+ export declare function renameFlagsUnsupported(outcome: NativePublishOutcome): boolean;
15
33
  export type ManagedSessionSecurityPolicy = "default" | "windows-existing-verify-first";
16
34
  export type ManagedStorageFailure = "migration_busy" | "binding_conflict" | "binding_invalid" | "destination_conflict" | "source_changed" | "unsafe_artifacts" | "artifact_capacity_exceeded" | "durability_failed" | "atomic_unavailable" | "durability_not_provable" | "migration_retired" | "managed_storage_unsupported";
17
35
  export interface ManagedStorageLock {
@@ -114,7 +132,7 @@ export declare function captureManagedFilePrefixNoFollow(pathname: string, maxBy
114
132
  export declare function captureManagedFileNoFollow(pathname: string): ManagedFileSnapshot;
115
133
  /** Atomically publishes bytes without replacing an existing destination. */
116
134
  export declare function publishManagedFileNoReplace(destination: string, bytes: Uint8Array, assertOwned?: () => void, root?: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy): Promise<void>;
117
- export declare function publishManagedFileNoReplaceSync(destination: string, bytes: Uint8Array, root?: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy): void;
135
+ export declare function publishManagedFileNoReplaceSync(destination: string, bytes: Uint8Array, root?: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy): ManagedFileSnapshot["identity"];
118
136
  /** Replace one managed regular file while retaining the secured staging fd through publication. */
119
137
  export declare function replaceManagedFileSync(destination: string, bytes: Uint8Array, root: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy, assertFence?: () => void, expectedDestination?: ManagedFileSnapshot["identity"]): void;
120
138
  export declare function replaceManagedFile(destination: string, bytes: Uint8Array, root: ManagedDirectoryRoot, policy?: ManagedSessionSecurityPolicy): Promise<void>;
@@ -2,8 +2,8 @@ export type NativePublishMutationState = "not_committed" | "committed" | "unknow
2
2
  export type NativePublishDurabilityState = "not_attempted" | "proven" | "not_provable";
3
3
  export type NativePublishReason = "none" | "destination_exists" | "atomic_unavailable" | "cross_device" | "permission_denied" | "io_failure" | "interrupted" | "invalid_request" | "identity_violation" | "durability_not_provable" | "unknown";
4
4
  export type NativePublishOperation = "direct_rename" | "retained_file" | "retained_tree";
5
- export type NativePublishPrimitive = "renameat2_noreplace" | "renameatx_np_excl" | "windows_rename_noreplace" | "unsupported" | "unknown";
6
- export type NativePublishPhase = "preflight" | "file_sync" | "rename" | "source_parent_sync" | "destination_parent_sync" | "terminal_identity" | "complete" | "unknown";
5
+ export type NativePublishPrimitive = "renameat2_noreplace" | "linkat_noreplace" | "mkdirat_renameat_noreplace" | "renameatx_np_excl" | "windows_rename_noreplace" | "unsupported" | "unknown";
6
+ export type NativePublishPhase = "preflight" | "file_sync" | "rename" | "source_unlink" | "source_parent_sync" | "destination_parent_sync" | "terminal_identity" | "complete" | "unknown";
7
7
  type SyncFailure = {
8
8
  phase: Exclude<NativePublishPhase, "preflight" | "file_sync" | "rename" | "complete" | "unknown">;
9
9
  parentRole: "source" | "destination" | "shared" | "staged_file";
@@ -333,6 +333,7 @@ export interface ResumeSessionIdentity {
333
333
  size: number;
334
334
  mtimeMs: number;
335
335
  mtimeNs: bigint;
336
+ ctimeNs?: bigint;
336
337
  sha256: string;
337
338
  }
338
339
  export interface ResumeTailResumable {
@@ -348,6 +349,13 @@ export interface ResumeTailError {
348
349
  reason: "missing" | "malformed" | "unstable" | "read-failed" | "legacy_migration_disabled";
349
350
  }
350
351
  export type SessionDirectoryMigrationPolicy = ManagedMigrationPolicy;
352
+ export type SessionAppendPersistenceFailurePhase = "current_append" | "prior_failure";
353
+ export declare class SessionAppendPersistenceError extends Error {
354
+ readonly phase: SessionAppendPersistenceFailurePhase;
355
+ readonly entryId: string;
356
+ readonly persistenceError: Error;
357
+ constructor(phase: SessionAppendPersistenceFailurePhase, entryId: string, persistenceError: Error);
358
+ }
351
359
  export declare class SessionManagedStorageError extends Error {
352
360
  readonly code = "managed_storage_unsupported";
353
361
  constructor();
@@ -838,6 +846,15 @@ export declare class SessionManager {
838
846
  applyEntryMessageUpdates(entries: readonly SessionMessageEntry[]): void;
839
847
  /** Write mutated custom-message entries back into the canonical entry store by id. */
840
848
  applyCustomMessageEntryUpdates(entries: readonly CustomMessageEntry[]): void;
849
+ /**
850
+ * Rehydrate the canonical transcript after a synchronous persistence failure.
851
+ *
852
+ * The failed append may have committed before reporting an uncertain outcome,
853
+ * so callers must not clear the sticky error or retry against the resident
854
+ * branch. Reloading the exact session file is the only supported recovery
855
+ * boundary for both managed and explicit persistent destinations.
856
+ */
857
+ recoverPersistenceFailure(): Promise<void>;
841
858
  /**
842
859
  * Rewrite the session file after in-place entry updates.
843
860
  * Use sparingly (e.g., pruning old tool outputs).
@@ -7,6 +7,7 @@ export interface SessionStorageStat {
7
7
  size: number;
8
8
  mtimeMs: number;
9
9
  mtimeNs: bigint;
10
+ ctimeNs: bigint;
10
11
  mtime: Date;
11
12
  isFile: boolean;
12
13
  }
@@ -16,6 +16,14 @@ export interface LoadContextFilesOptions {
16
16
  /** Working directory to start walking up from. Default: getProjectDir() */
17
17
  cwd?: string;
18
18
  }
19
+ export interface ProjectContextFilesResult {
20
+ contextFiles: Array<{
21
+ path: string;
22
+ content: string;
23
+ depth?: number;
24
+ }>;
25
+ warnings: string[];
26
+ }
19
27
  /**
20
28
  * Load all context files using the capability API.
21
29
  * Returns {path, content, depth} entries for all discovered context files.
@@ -24,6 +32,8 @@ export interface LoadContextFilesOptions {
24
32
  * prominent. User-home files from foreign providers (`~/.claude/CLAUDE.md`,
25
33
  * `~/.codex/AGENTS.md`, …) stay excluded — only gjc's own user config applies.
26
34
  */
35
+ export declare function loadProjectContextFilesResult(options?: LoadContextFilesOptions): Promise<ProjectContextFilesResult>;
36
+ /** Load project context files without exposing discovery diagnostics. */
27
37
  export declare function loadProjectContextFiles(options?: LoadContextFilesOptions): Promise<Array<{
28
38
  path: string;
29
39
  content: string;
@@ -93,10 +103,11 @@ export interface BuildSystemPromptOptions {
93
103
  */
94
104
  subagent?: boolean;
95
105
  }
96
- /** Result of building provider-facing system prompt messages. */
97
106
  export interface BuildSystemPromptResult {
98
107
  /** Ordered system prompt blocks. Providers should preserve entries as distinct messages/blocks. */
99
108
  systemPrompt: string[];
109
+ /** Context-file discovery warnings visible to SDK and session callers. */
110
+ warnings: string[];
100
111
  }
101
112
  export interface BuildVolatileProjectContextOptions {
102
113
  cwd?: string;
@@ -1,14 +1,22 @@
1
- export declare const COMPOSER_BASH_POLICY_ERROR = "Composer bash policy blocked repository file I/O. Use find, search, read, and edit tools for file discovery, file inspection, and file mutation.";
1
+ import { type ComposerBashPolicyToolSurface } from "@gajae-code/ai/providers/composer-discipline";
2
+ export declare const COMPOSER_BASH_POLICY_ERROR: string;
3
+ export declare const CURSOR_COMPOSER_BASH_POLICY_ERROR: string;
2
4
  type ComposerBashPolicyResult = {
3
5
  allowed: true;
4
6
  } | {
5
7
  allowed: false;
6
8
  reason: string;
7
9
  message: string;
10
+ surface: ComposerBashPolicyToolSurface;
8
11
  };
9
12
  export declare function isComposerBashPolicyModel(modelId: string | undefined): boolean;
13
+ export declare function resolveComposerBashPolicyToolSurface(input: {
14
+ modelId?: string;
15
+ provider?: string;
16
+ }): ComposerBashPolicyToolSurface;
10
17
  export declare function checkComposerBashPolicy(input: {
11
18
  modelId?: string;
19
+ provider?: string;
12
20
  commands: readonly string[];
13
21
  }): ComposerBashPolicyResult;
14
22
  export {};
@@ -239,7 +239,7 @@ export interface ComputerScreenshotDetails {
239
239
  originX?: number;
240
240
  originY?: number;
241
241
  displayEpoch?: number;
242
- captureId?: string;
242
+ captureId?: number;
243
243
  pngBytes?: number;
244
244
  path?: string;
245
245
  }
@@ -258,11 +258,44 @@ export interface ComputerToolDetails {
258
258
  keys?: string[];
259
259
  ms?: number;
260
260
  screenshot?: ComputerScreenshotDetails;
261
+ primaryError?: {
262
+ code: string;
263
+ message: string;
264
+ };
261
265
  supervisor?: string;
262
266
  steps?: ComputerToolDetails[];
263
267
  meta?: OutputMeta;
264
268
  }
269
+ type NativeBatchAction = {
270
+ action: "screenshot" | "click" | "double_click" | "move" | "drag" | "scroll" | "type" | "keypress" | "wait";
271
+ x?: number;
272
+ y?: number;
273
+ toX?: number;
274
+ toY?: number;
275
+ scrollX?: number;
276
+ scrollY?: number;
277
+ button?: string;
278
+ text?: string;
279
+ keys?: string[];
280
+ ms?: number;
281
+ timeoutMs?: number;
282
+ timeoutGroup?: number;
283
+ };
284
+ type NativeBatchStepResult = {
285
+ index: number;
286
+ action: string;
287
+ screenshot?: NativeScreenshot;
288
+ };
289
+ type NativeBatchResult = {
290
+ results: NativeBatchStepResult[];
291
+ failureCode?: string;
292
+ failureIndex?: number;
293
+ failureMessage?: string;
294
+ primaryFailureCode?: string;
295
+ primaryFailureMessage?: string;
296
+ };
265
297
  type NativeController = {
298
+ executeBatch?: (expectedEpoch: number | undefined, actions: NativeBatchAction[], timeoutMs?: number, signal?: AbortSignal) => Promise<NativeBatchResult> | NativeBatchResult;
266
299
  screenshot?: () => Promise<NativeScreenshot> | NativeScreenshot;
267
300
  click?: (expectedEpoch: number | undefined, x: number, y: number, button?: string) => void;
268
301
  doubleClick?: (expectedEpoch: number | undefined, x: number, y: number, button?: string) => void;
@@ -282,7 +315,7 @@ type NativeScreenshot = {
282
315
  originX?: number;
283
316
  originY?: number;
284
317
  displayEpoch?: number;
285
- captureId?: string;
318
+ captureId?: number;
286
319
  };
287
320
  export type ComputerControllerFactory = () => NativeController;
288
321
  export declare const COMPUTER_DISABLED_CODE = "COMPUTER_DISABLED";
@@ -25,6 +25,7 @@ export interface TodoPhase {
25
25
  export interface TodoWriteToolDetails {
26
26
  phases: TodoPhase[];
27
27
  storage: "session" | "memory";
28
+ failureKind?: "payload_rejected" | "persistence";
28
29
  }
29
30
  declare function validateRawTodoArguments(arguments_: Record<string, unknown>): RawArgumentValidationResult;
30
31
  declare const todoWriteSchema: z.ZodObject<{
@@ -117,6 +118,7 @@ export declare const todoWriteToolRenderer: {
117
118
  text?: string;
118
119
  }>;
119
120
  details?: TodoWriteToolDetails;
121
+ isError?: boolean;
120
122
  }, options: RenderResultOptions, uiTheme: Theme, _args?: TodoWriteRenderArgs): Component;
121
123
  mergeCallAndResult: boolean;
122
124
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@gajae-code/coding-agent",
4
- "version": "0.12.8",
4
+ "version": "0.12.11",
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.3.0",
54
54
  "@babel/parser": "^7.29.3",
55
55
  "@mozilla/readability": "^0.6.0",
56
- "@gajae-code/stats": "0.12.8",
57
- "@gajae-code/agent-core": "0.12.8",
58
- "@gajae-code/ai": "0.12.8",
59
- "@gajae-code/bridge-client": "0.12.8",
60
- "@gajae-code/natives": "0.12.8",
61
- "@gajae-code/tui": "0.12.8",
62
- "@gajae-code/utils": "0.12.8",
56
+ "@gajae-code/stats": "0.12.11",
57
+ "@gajae-code/agent-core": "0.12.11",
58
+ "@gajae-code/ai": "0.12.11",
59
+ "@gajae-code/bridge-client": "0.12.11",
60
+ "@gajae-code/natives": "0.12.11",
61
+ "@gajae-code/tui": "0.12.11",
62
+ "@gajae-code/utils": "0.12.11",
63
63
  "@puppeteer/browsers": "^2.13.0",
64
64
  "@types/turndown": "5.0.6",
65
65
  "@xterm/headless": "^6.0.0",
@@ -1531,13 +1531,26 @@ function exactTeamRuntimeSendKeysRanges(contents: string): ShellRange[] {
1531
1531
  return [];
1532
1532
 
1533
1533
  const continuationBody = contents.slice(continuationRange.start, continuationRange.end);
1534
+ // The frozen argv may address the pane either through `worker.pane_id` directly or
1535
+ // through a local binding that was proven non-empty first (the optional field does
1536
+ // not narrow for the type checker). Either way both send operations must name the
1537
+ // identical pane token, and a local token must come from a checked `worker.pane_id`.
1534
1538
  const continuationArgs =
1535
- /const\s+args\s*=\s*Object\.freeze\(\s*\[\s*["']send-keys["']\s*,\s*["']-l["']\s*,\s*["']-t["']\s*,\s*worker\.pane_id\s*,\s*continuationPrompt\s*,\s*["'];["']\s*,\s*["']send-keys["']\s*,\s*["']-t["']\s*,\s*worker\.pane_id\s*,\s*["']Enter["']\s*,?\s*\]\s*\)\s*;/.exec(
1539
+ /const\s+args(?:\s*:\s*readonly\s+string\[\])?\s*=\s*Object\.freeze\(\s*\[\s*["']send-keys["']\s*,\s*["']-l["']\s*,\s*["']-t["']\s*,\s*(worker\.pane_id|[A-Za-z_$][\w$]*)\s*,\s*continuationPrompt\s*,\s*["'];["']\s*,\s*["']send-keys["']\s*,\s*["']-t["']\s*,\s*(worker\.pane_id|[A-Za-z_$][\w$]*)\s*,\s*["']Enter["']\s*,?\s*\]\s*\)\s*;/.exec(
1536
1540
  continuationBody,
1537
1541
  );
1542
+ const paneToken = continuationArgs?.[1];
1543
+ const paneTokenIsProvenLocal =
1544
+ paneToken !== undefined &&
1545
+ paneToken !== "worker.pane_id" &&
1546
+ new RegExp(`const\\s+${paneToken}\\s*=\\s*worker\\.pane_id\\s*;`).test(continuationBody) &&
1547
+ new RegExp(`if\\s*\\(\\s*!${paneToken}\\s*\\)\\s*return\\b`).test(continuationBody);
1538
1548
  if (
1539
1549
  !continuationArgs ||
1550
+ continuationArgs[1] !== continuationArgs[2] ||
1551
+ !(paneToken === "worker.pane_id" || paneTokenIsProvenLocal) ||
1540
1552
  !continuationBody.includes("const revalidationReason =") ||
1553
+ !/if\s*\(\s*revalidationReason\s*\)\s*(?:\{\s*return\b[^}]*;?\s*\}|return\b[^;]*;)/.test(continuationBody) ||
1541
1554
  !continuationBody.includes("await createJsonNoClobber(") ||
1542
1555
  !continuationBody.includes('type: "literal-send"') ||
1543
1556
  !continuationBody.includes('type: "key-send"') ||
@@ -69,6 +69,7 @@ export interface NotifyCommandArgs {
69
69
  slackChannelId?: string;
70
70
  slackAuthorizedUserId?: string;
71
71
  redact?: boolean;
72
+ forceDaemonLock?: boolean;
72
73
  probe?: boolean;
73
74
  message?: string;
74
75
  }
@@ -193,7 +194,12 @@ export function parseNotifyArgs(args: string[]): NotifyCommandArgs | undefined {
193
194
  message: typeof flags.get("--message") === "string" ? (flags.get("--message") as string) : undefined,
194
195
  };
195
196
  }
196
- if (action === "recovery") return args.length === 2 ? { action, rawArgs: [] } : undefined;
197
+ if (action === "recovery") {
198
+ const flags = parseFlags(args.slice(2), new Set(), new Set(["--force-daemon-lock"]));
199
+ return flags
200
+ ? { action, rawArgs: args.slice(2), forceDaemonLock: flags.get("--force-daemon-lock") === true }
201
+ : undefined;
202
+ }
197
203
  if (action === "daemon-internal") {
198
204
  return {
199
205
  action,
@@ -224,7 +230,7 @@ export async function runNotifyCommand(cmd: NotifyCommandArgs, deps: NotifyComma
224
230
  await runTest(deps, cmd);
225
231
  return;
226
232
  case "recovery":
227
- await runRecovery(deps);
233
+ await runRecovery(deps, cmd.forceDaemonLock);
228
234
  return;
229
235
  case "daemon-internal":
230
236
  if (cmd.smoke) {
@@ -716,9 +722,9 @@ async function runTest(deps: NotifyCommandDeps, cmd: NotifyCommandArgs): Promise
716
722
  else if (!result.ok) process.exitCode = 1;
717
723
  }
718
724
 
719
- async function runRecovery(deps: NotifyCommandDeps): Promise<void> {
725
+ async function runRecovery(deps: NotifyCommandDeps, forceDaemonLock = false): Promise<void> {
720
726
  const settings = await getSettings(deps);
721
- const report = await recoverNotifications({ settings });
727
+ const report = await recoverNotifications({ settings, forceDaemonLock });
722
728
  process.stdout.write(`${formatNotificationRecoveryReport(report)}\n`);
723
729
  }
724
730
 
@@ -737,14 +743,14 @@ ${chalk.bold("Usage:")}
737
743
  ${APP_NAME} notify status
738
744
  ${APP_NAME} notify health [--provider telegram|discord|slack] [--probe]
739
745
  ${APP_NAME} notify test [--provider telegram|discord|slack] [--message <text>]
740
- ${APP_NAME} notify recovery
746
+ ${APP_NAME} notify recovery [--force-daemon-lock]
741
747
 
742
748
  ${chalk.bold("Subcommands:")}
743
749
  setup Pair Telegram or atomically save and activate complete Discord/Slack settings
744
750
  status Show global master and provider configured/repair/desired/effective state without secrets
745
751
  health Report selected provider state; --probe uses REST only and never opens Gateway/Socket Mode
746
752
  test Send a one-off test through one selected or uniquely effective provider
747
- recovery Clear dead-owner daemon locks and stale per-session endpoint files (never touches a live owner)
753
+ recovery Clear dead-owner daemon locks and stale per-session endpoint files (never touches a live owner); --force-daemon-lock retries only with the same fail-closed dead-owner proof
748
754
 
749
755
  ${chalk.bold("Examples:")}
750
756
  ${APP_NAME} notify setup