@gajae-code/coding-agent 0.12.8 → 0.12.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 +29 -0
- package/dist/types/cli/notify-cli.d.ts +1 -0
- package/dist/types/commands/sdk.d.ts +23 -0
- package/dist/types/discovery/agents-md.d.ts +11 -1
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +23 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +8 -0
- package/dist/types/session/internal/native-publish-outcome.d.ts +2 -2
- package/dist/types/system-prompt.d.ts +12 -1
- package/dist/types/tools/composer-bash-policy.d.ts +9 -1
- package/package.json +8 -8
- package/scripts/verify-gjc-sdk-canonicalization.ts +14 -1
- package/src/cli/notify-cli.ts +12 -6
- package/src/commands/sdk.ts +54 -0
- package/src/config/model-profiles.ts +8 -0
- package/src/config/model-registry.ts +4 -1
- package/src/discovery/agents-md.ts +76 -19
- package/src/gjc-runtime/psmux-detect.ts +12 -16
- package/src/gjc-runtime/tmux-sessions.ts +8 -1
- package/src/internal-urls/docs-index.generated.ts +4 -3
- package/src/modes/components/extensions/inspector-panel.ts +9 -5
- package/src/modes/controllers/selector-controller.ts +61 -43
- package/src/modes/print-mode.ts +3 -0
- package/src/modes/types.ts +6 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/notification-service.ts +140 -7
- package/src/sdk/bus/telegram-daemon-contract.ts +2 -2
- package/src/sdk/session.ts +24 -7
- package/src/session/agent-session.ts +65 -13
- package/src/session/internal/managed-session-storage.ts +36 -2
- package/src/session/internal/native-publish-outcome.ts +12 -2
- package/src/slash-commands/builtin-registry.ts +35 -11
- package/src/system-prompt.ts +28 -15
- package/src/tools/bash.ts +3 -1
- package/src/tools/composer-bash-policy.ts +21 -5
- package/src/utils/command-args.ts +19 -30
- package/src/utils/lang-from-path.ts +2 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.12.10] - 2026-08-03
|
|
6
|
+
|
|
7
|
+
## [0.12.9] - 2026-08-03
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `/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.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
- 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.
|
|
17
|
+
- The Extension Control Center inspector no longer crashes when a narrow two-column layout leaves its preview pane fewer than two columns wide.
|
|
18
|
+
- Prompt-template positional arguments now preserve literal `$@` and `$ARGUMENTS` text instead of recursively expanding it during placeholder substitution.
|
|
19
|
+
- 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).
|
|
20
|
+
- 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).
|
|
21
|
+
- 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).
|
|
22
|
+
- 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).
|
|
23
|
+
- 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.
|
|
24
|
+
- Dead-owner notification recovery now preserves a machine-readable transition block, marker-age diagnostics, and safe force-recovery guidance without weakening ownership proofs (#3762).
|
|
25
|
+
- 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.
|
|
26
|
+
- Syntax highlighting now recognizes special filenames such as `CMakeLists.txt`, `Dockerfile.*`, `Makefile`, and `.env.*` before generic filename extensions.
|
|
27
|
+
|
|
5
28
|
## [0.12.8] - 2026-08-02
|
|
6
29
|
### Added
|
|
7
30
|
|
|
8
31
|
- 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.
|
|
32
|
+
- 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
33
|
|
|
10
34
|
### Added
|
|
11
35
|
|
|
@@ -13,10 +37,14 @@
|
|
|
13
37
|
|
|
14
38
|
### Fixed
|
|
15
39
|
|
|
40
|
+
- 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).
|
|
41
|
+
- 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
42
|
- Ordinary `ask` selectors now bound long question premises and page through every premise row without skipping rows hidden by overflow indicators (#3675).
|
|
17
43
|
- 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
44
|
- 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
45
|
- 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.
|
|
46
|
+
- Standalone `AGENTS.md` ancestor discovery now bounds directory traversal, per-file reads, and aggregate instruction bytes while surfacing content-free omission warnings (#3722).
|
|
47
|
+
|
|
20
48
|
## [0.12.7] - 2026-07-31
|
|
21
49
|
|
|
22
50
|
## [0.12.6] - 2026-07-31
|
|
@@ -26,6 +54,7 @@
|
|
|
26
54
|
|
|
27
55
|
### Fixed
|
|
28
56
|
|
|
57
|
+
- 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
58
|
- 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
59
|
- `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
60
|
- 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.
|
|
@@ -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
|
-
|
|
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 {};
|
|
@@ -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,10 @@ 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
|
|
56
|
+
* Generation 42 applies first-class provider-settings admission to Telegram
|
|
57
57
|
* lifecycle controls.
|
|
58
58
|
*/
|
|
59
|
-
export declare const DAEMON_GENERATION =
|
|
59
|
+
export declare const DAEMON_GENERATION = 42;
|
|
60
60
|
/**
|
|
61
61
|
* Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
|
|
62
62
|
* all builds published before this field existed; epoch 2 covered generation 29;
|
|
@@ -12,6 +12,14 @@ export declare class ManagedPublishError extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
/** Cleanup is safe only when the native move reports that it did not commit. */
|
|
14
14
|
export declare function mayCleanManagedTreeStaging(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* A filesystem that implements no `renameat2` rename flag at all rejects the no-replace publish
|
|
17
|
+
* before mutating anything: NFS answers `EINVAL` (`invalid_request`) and kernels older than 3.15
|
|
18
|
+
* answer `ENOSYS` (`atomic_unavailable`). Only those two pre-mutation outcomes authorize the
|
|
19
|
+
* `linkat` stand-in; every other failure describes a publish that was actually attempted, and
|
|
20
|
+
* retrying it under a different primitive could publish twice.
|
|
21
|
+
*/
|
|
22
|
+
export declare function renameFlagsUnsupported(outcome: NativePublishOutcome): boolean;
|
|
15
23
|
export type ManagedSessionSecurityPolicy = "default" | "windows-existing-verify-first";
|
|
16
24
|
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
25
|
export interface ManagedStorageLock {
|
|
@@ -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";
|
|
@@ -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
|
-
|
|
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 {};
|
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.
|
|
4
|
+
"version": "0.12.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.3.0",
|
|
54
54
|
"@babel/parser": "^7.29.3",
|
|
55
55
|
"@mozilla/readability": "^0.6.0",
|
|
56
|
-
"@gajae-code/stats": "0.12.
|
|
57
|
-
"@gajae-code/agent-core": "0.12.
|
|
58
|
-
"@gajae-code/ai": "0.12.
|
|
59
|
-
"@gajae-code/bridge-client": "0.12.
|
|
60
|
-
"@gajae-code/natives": "0.12.
|
|
61
|
-
"@gajae-code/tui": "0.12.
|
|
62
|
-
"@gajae-code/utils": "0.12.
|
|
56
|
+
"@gajae-code/stats": "0.12.10",
|
|
57
|
+
"@gajae-code/agent-core": "0.12.10",
|
|
58
|
+
"@gajae-code/ai": "0.12.10",
|
|
59
|
+
"@gajae-code/bridge-client": "0.12.10",
|
|
60
|
+
"@gajae-code/natives": "0.12.10",
|
|
61
|
+
"@gajae-code/tui": "0.12.10",
|
|
62
|
+
"@gajae-code/utils": "0.12.10",
|
|
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"') ||
|
package/src/cli/notify-cli.ts
CHANGED
|
@@ -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")
|
|
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
|
package/src/commands/sdk.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { applyStartupModelProfiles, createSessionManager } from "../main";
|
|
|
9
9
|
import { initializeExtensions } from "../modes/runtime-init";
|
|
10
10
|
import { ACP_MCP_REQUEST_TIMEOUT_MS, ACP_MCP_STARTUP_HEADROOM_MS } from "../sdk/acp/mcp";
|
|
11
11
|
import { Broker } from "../sdk/broker/broker";
|
|
12
|
+
import { readBrokerDiscovery } from "../sdk/broker/discovery";
|
|
12
13
|
import { completeBrokerProcess } from "../sdk/broker/internal";
|
|
13
14
|
import {
|
|
14
15
|
type LifecycleTranscriptEvidence,
|
|
@@ -58,6 +59,54 @@ export async function lifecycleArgs(
|
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* How long a session host tolerates the complete absence of a live broker
|
|
64
|
+
* publication before treating itself as orphaned. Hosts intentionally survive
|
|
65
|
+
* broker restarts (a replacement broker republishes discovery within seconds),
|
|
66
|
+
* so this must comfortably exceed a restart window while still bounding the
|
|
67
|
+
* lifetime of hosts whose broker is gone for good — otherwise every crashed or
|
|
68
|
+
* torn-down broker leaks a detached multi-hundred-megabyte host forever.
|
|
69
|
+
*/
|
|
70
|
+
export const SESSION_HOST_BROKER_ABSENCE_GRACE_MS = 10 * 60_000;
|
|
71
|
+
const SESSION_HOST_BROKER_POLL_MS = 15_000;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolves only once no live broker publication has been observable in
|
|
75
|
+
* `agentDir` for the full grace window. A reappearing broker (including a
|
|
76
|
+
* replacement with a different pid) resets the window; an unreadable
|
|
77
|
+
* publication is not proof of orphanhood but accrues against the same bound.
|
|
78
|
+
*/
|
|
79
|
+
export async function watchSessionHostBrokerLiveness(deps: {
|
|
80
|
+
agentDir: string;
|
|
81
|
+
now?: () => number;
|
|
82
|
+
sleep?: (ms: number) => Promise<void>;
|
|
83
|
+
readDiscovery?: (agentDir: string) => Promise<unknown>;
|
|
84
|
+
graceMs?: number;
|
|
85
|
+
pollMs?: number;
|
|
86
|
+
}): Promise<void> {
|
|
87
|
+
const now = deps.now ?? Date.now;
|
|
88
|
+
const sleep = deps.sleep ?? (async ms => await Bun.sleep(ms));
|
|
89
|
+
const readDiscovery = deps.readDiscovery ?? readBrokerDiscovery;
|
|
90
|
+
const graceMs = deps.graceMs ?? SESSION_HOST_BROKER_ABSENCE_GRACE_MS;
|
|
91
|
+
const pollMs = deps.pollMs ?? SESSION_HOST_BROKER_POLL_MS;
|
|
92
|
+
let absentSince: number | null = null;
|
|
93
|
+
for (;;) {
|
|
94
|
+
let live: unknown = null;
|
|
95
|
+
try {
|
|
96
|
+
live = await readDiscovery(deps.agentDir);
|
|
97
|
+
} catch {
|
|
98
|
+
// Transient read failures are ambiguity, not proof of orphanhood.
|
|
99
|
+
}
|
|
100
|
+
if (live) {
|
|
101
|
+
absentSince = null;
|
|
102
|
+
} else {
|
|
103
|
+
absentSince ??= now();
|
|
104
|
+
if (now() - absentSince >= graceMs) return;
|
|
105
|
+
}
|
|
106
|
+
await sleep(pollMs);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
61
110
|
type LifecycleTranscriptSource = {
|
|
62
111
|
cwd: string;
|
|
63
112
|
path: string;
|
|
@@ -505,6 +554,11 @@ export async function runSessionHost(
|
|
|
505
554
|
}
|
|
506
555
|
process.once("SIGTERM", stop);
|
|
507
556
|
process.once("SIGINT", stop);
|
|
557
|
+
// A detached host whose broker is gone for good would otherwise live (and
|
|
558
|
+
// hold its session's memory) forever; reap it through the same graceful
|
|
559
|
+
// teardown a SIGTERM would take once the bounded absence grace elapses.
|
|
560
|
+
await watchSessionHostBrokerLiveness({ agentDir });
|
|
561
|
+
stop();
|
|
508
562
|
await new Promise<void>(() => {});
|
|
509
563
|
}
|
|
510
564
|
|
|
@@ -291,6 +291,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
291
291
|
architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
292
292
|
critic: "alibaba-token-plan/glm-5.2:high",
|
|
293
293
|
}),
|
|
294
|
+
profile("alibaba-token-plan-pro", ["alibaba-token-plan"], {
|
|
295
|
+
default: "alibaba-token-plan/qwen3.8-max-preview:medium",
|
|
296
|
+
executor: "alibaba-token-plan/deepseek-v4-flash-0731:max",
|
|
297
|
+
planner: "alibaba-token-plan/glm-5.2:high",
|
|
298
|
+
architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
299
|
+
critic: "alibaba-token-plan/glm-5.2:xhigh",
|
|
300
|
+
}),
|
|
294
301
|
profile("alibaba-token-plan-qwenmaxxing", ["alibaba-token-plan"], {
|
|
295
302
|
default: "alibaba-token-plan/qwen3.8-max-preview:medium",
|
|
296
303
|
executor: "alibaba-token-plan/qwen3.8-max-preview:low",
|
|
@@ -361,6 +368,7 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
361
368
|
"minimax-medium": { displayName: "MiniMax Medium", providerGroup: "MINIMAX" },
|
|
362
369
|
"minimax-pro": { displayName: "MiniMax Pro", providerGroup: "MINIMAX" },
|
|
363
370
|
"alibaba-token-plan-balanced": { displayName: "Balanced", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
371
|
+
"alibaba-token-plan-pro": { displayName: "Pro", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
364
372
|
"alibaba-token-plan-qwenmaxxing": { displayName: "QwenMaxxing", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
365
373
|
"opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
|
|
366
374
|
"codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
|
|
@@ -331,7 +331,10 @@ function validateProviderConfiguration(
|
|
|
331
331
|
throw new Error(
|
|
332
332
|
mode === "runtime-register"
|
|
333
333
|
? `Provider ${providerName}: "apiKey" or "oauth" is required when defining models.`
|
|
334
|
-
: `Provider ${providerName}:
|
|
334
|
+
: `Provider ${providerName}: custom models need a credential source, but none is configured. ` +
|
|
335
|
+
`"auth" only selects the scheme ("auth: apiKey" does not supply a key). ` +
|
|
336
|
+
`Fix by adding "apiKeyEnv: <ENV_VAR>" (recommended) or "apiKey: <literal-key>", ` +
|
|
337
|
+
`or set "auth: none" if the endpoint is genuinely unauthenticated.`,
|
|
335
338
|
);
|
|
336
339
|
}
|
|
337
340
|
}
|