@gajae-code/coding-agent 0.15.6 → 0.16.1
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 +99 -0
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +14 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +27 -8
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +79 -2
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +21 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +9 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +12 -5
- package/dist/types/sdk/cli/session-cli.d.ts +20 -3
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +70 -17
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +3 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +6 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +52 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +79 -2
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +13 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +140 -23
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +135 -28
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +178 -23
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +291 -42
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +27 -8
- package/src/config/settings.ts +51 -15
- package/src/coordinator-mcp/projection-scan.ts +27 -6
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +115 -21
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +213 -14
- package/src/gjc-runtime/session-state-sidecar.ts +938 -39
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +61 -3
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +610 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +30 -2
- package/src/modes/controllers/selector-controller.ts +200 -98
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +1646 -27
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +81 -3
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +440 -75
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +591 -0
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +43 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +84 -7
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +41 -15
- package/src/sdk/cli/session-cli.ts +297 -26
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +101 -40
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +29 -2
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +673 -14
- package/src/sdk/host/session-runtime.ts +674 -103
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +17 -4
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +105 -3
- package/src/sdk/protocol/operation-registry.ts +14 -4
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +219 -118
- package/src/sdk/session.ts +416 -195
- package/src/sdk/startup-capability.ts +1 -0
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1628 -318
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +850 -97
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +412 -75
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +10 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -293,6 +293,43 @@ function resolveSystemChromium(): string | undefined {
|
|
|
293
293
|
return found;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
export function resolveSystemChromiumForTest(): string | undefined {
|
|
297
|
+
return resolveSystemChromium();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function resetSystemChromiumForTest(): void {
|
|
301
|
+
resolvedChromium = undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export function headlessExecutableForProfileReuse(): string | undefined {
|
|
305
|
+
return resolveSystemChromium() ?? trustedBrowserEnv("PUPPETEER_EXECUTABLE_PATH");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* On macOS the Cookies DB is encrypted with a Keychain key whose service name is
|
|
310
|
+
* brand-specific ("Chrome Safe Storage" vs "Chromium Safe Storage"). A Chrome profile
|
|
311
|
+
* copied into a bundled Chromium therefore decrypts to garbage with no warning.
|
|
312
|
+
* Return true only when the headless executable can actually decrypt the discovered profile.
|
|
313
|
+
*/
|
|
314
|
+
export function canDecryptProfileCookies(
|
|
315
|
+
executablePath: string | undefined,
|
|
316
|
+
discoveredUserDataDir: string | undefined,
|
|
317
|
+
platform: NodeJS.Platform = process.platform,
|
|
318
|
+
): boolean {
|
|
319
|
+
if (platform !== "darwin") return true;
|
|
320
|
+
if (!discoveredUserDataDir) return true;
|
|
321
|
+
if (!executablePath) return false;
|
|
322
|
+
const lowerExe = executablePath.toLowerCase();
|
|
323
|
+
const lowerDir = discoveredUserDataDir.toLowerCase();
|
|
324
|
+
const isChromiumExe = lowerExe.includes("chromium");
|
|
325
|
+
const isChromeExe = lowerExe.includes("google chrome") || lowerExe.includes("/google/chrome");
|
|
326
|
+
const isChromiumProfile = lowerDir.includes("chromium");
|
|
327
|
+
const isChromeProfile = lowerDir.includes("google") && lowerDir.includes("chrome");
|
|
328
|
+
if (isChromeProfile && isChromiumExe && !isChromeExe) return false;
|
|
329
|
+
if (isChromiumProfile && isChromeExe) return false;
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
|
|
296
333
|
/** Edge is Chromium-based but keeps its own profile format under its own user data root. */
|
|
297
334
|
const EDGE_EXECUTABLE_PATTERN =
|
|
298
335
|
/(?:^|[/\\])(?:msedge(?:\.exe)?|microsoft-edge(?:-(?:stable|beta|dev|canary))?|com\.microsoft\.Edge|Microsoft Edge(?: Beta| Dev| Canary)?)$/i;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import * as os from "node:os";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { logger } from "@gajae-code/utils";
|
|
4
5
|
import type { Subprocess } from "bun";
|
|
@@ -12,7 +13,14 @@ import {
|
|
|
12
13
|
killExistingByPath,
|
|
13
14
|
waitForCdp,
|
|
14
15
|
} from "./attach";
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
BROWSER_PROTOCOL_TIMEOUT_MS,
|
|
18
|
+
canDecryptProfileCookies,
|
|
19
|
+
headlessExecutableForProfileReuse,
|
|
20
|
+
launchHeadlessBrowser,
|
|
21
|
+
loadPuppeteer,
|
|
22
|
+
type UserAgentOverride,
|
|
23
|
+
} from "./launch";
|
|
16
24
|
import { defaultDiscoveryEnv } from "./profile-discovery";
|
|
17
25
|
import type { ProfileReusePosture } from "./profile-posture";
|
|
18
26
|
import { resolveProfileReuse } from "./profile-reuse";
|
|
@@ -55,6 +63,17 @@ type SpawnedChromeProfileKind = Extract<BrowserKind, { kind: "chrome-profile" }>
|
|
|
55
63
|
const browsers = new Map<string, BrowserHandle>();
|
|
56
64
|
const browserOpenPromises = new Map<string, Promise<BrowserHandle>>();
|
|
57
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Chrome profile warm-up dirs this module created, keyed by the handle that owns one.
|
|
68
|
+
*
|
|
69
|
+
* Ownership is a registry-internal fact, not a property of the handle: `BrowserHandle`
|
|
70
|
+
* is exported and mutable, so a field would let any caller point recursive deletion at
|
|
71
|
+
* an arbitrary path. Recording the dir here — only on the launch path that created it —
|
|
72
|
+
* means disposal can delete exactly what this module made and nothing else. A path
|
|
73
|
+
* prefix check would not prove ownership; an entry in this map does.
|
|
74
|
+
*/
|
|
75
|
+
const ownedWarmupDirs = new WeakMap<BrowserHandle, string>();
|
|
76
|
+
|
|
58
77
|
/**
|
|
59
78
|
* Upper bound on the CDP `browser.close()` round-trip during a forced (signal-path)
|
|
60
79
|
* teardown before we fall back to killing the Chrome process tree. Only applies when
|
|
@@ -110,6 +129,19 @@ export function browserKeyForTest(
|
|
|
110
129
|
return browserKey(kind, geo, opts.profileReuse);
|
|
111
130
|
}
|
|
112
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Create and register a warm-up dir as registry-owned for `handle`.
|
|
134
|
+
*
|
|
135
|
+
* Exists so tests can exercise disposal without spawning Chrome. The caller cannot
|
|
136
|
+
* supply the deletion target: this module creates the directory itself before recording
|
|
137
|
+
* ownership, preserving the same invariant as the production launch path.
|
|
138
|
+
*/
|
|
139
|
+
export function createOwnedWarmupDirForTest(handle: BrowserHandle): string {
|
|
140
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "gjc-profile-warmup-"));
|
|
141
|
+
ownedWarmupDirs.set(handle, dir);
|
|
142
|
+
return dir;
|
|
143
|
+
}
|
|
144
|
+
|
|
113
145
|
export async function acquireBrowser(kind: BrowserKind, opts: AcquireBrowserOptions): Promise<BrowserHandle> {
|
|
114
146
|
const geo = kind.kind === "headless" ? normalizeGeo(opts.geo) : undefined;
|
|
115
147
|
const key = browserKey(kind, geo, opts.profileReuse);
|
|
@@ -148,17 +180,38 @@ async function openBrowserHandle(
|
|
|
148
180
|
discoveryEnv: defaultDiscoveryEnv(fs.existsSync),
|
|
149
181
|
});
|
|
150
182
|
if (reuse.warning) logger.warn(reuse.warning);
|
|
151
|
-
if (reuse.mode === "real" && reuse.warmupDir) {
|
|
183
|
+
if (reuse.mode === "real" && reuse.warmupDir && reuse.discovered) {
|
|
184
|
+
const executablePath = headlessExecutableForProfileReuse();
|
|
185
|
+
if (!canDecryptProfileCookies(executablePath, reuse.discovered.userDataDir)) {
|
|
186
|
+
try {
|
|
187
|
+
fs.rmSync(reuse.warmupDir, { recursive: true, force: true });
|
|
188
|
+
} catch {}
|
|
189
|
+
const hint = executablePath
|
|
190
|
+
? `Real Chrome profile at ${reuse.discovered.userDataDir} cannot be decrypted by ${executablePath} on macOS (Chrome Safe Storage vs Chromium Safe Storage Keychain mismatch). Using synthetic browser state instead. Set PUPPETEER_EXECUTABLE_PATH to the matching Chrome/Chromium binary.`
|
|
191
|
+
: `Real Chrome profile at ${reuse.discovered.userDataDir} found but no compatible browser executable is available on macOS (bundled Chromium cannot decrypt Chrome's cookies — Chrome Safe Storage vs Chromium Safe Storage). Using synthetic browser state instead. Set PUPPETEER_EXECUTABLE_PATH to /Applications/Google Chrome.app/Contents/MacOS/Google Chrome or install Chrome.`;
|
|
192
|
+
logger.warn(hint);
|
|
193
|
+
} else {
|
|
194
|
+
profileWarmupDir = reuse.warmupDir;
|
|
195
|
+
}
|
|
196
|
+
} else if (reuse.mode === "real" && reuse.warmupDir) {
|
|
152
197
|
profileWarmupDir = reuse.warmupDir;
|
|
153
198
|
}
|
|
154
199
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
200
|
+
let browser: Browser;
|
|
201
|
+
try {
|
|
202
|
+
browser = await launchHeadlessBrowser({
|
|
203
|
+
headless: kind.headless,
|
|
204
|
+
viewport: opts.viewport,
|
|
205
|
+
profileWarmupDir,
|
|
206
|
+
...(geo ? { geo } : {}),
|
|
207
|
+
});
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// Launch failed after the warm-up copy landed: drop the temp dir here,
|
|
210
|
+
// because no handle will exist to carry it into releaseBrowser().
|
|
211
|
+
removeWarmupDir(profileWarmupDir);
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
const handle: BrowserHandle = {
|
|
162
215
|
key,
|
|
163
216
|
|
|
164
217
|
kind,
|
|
@@ -168,6 +221,11 @@ async function openBrowserHandle(
|
|
|
168
221
|
refCount: 0,
|
|
169
222
|
stealth: { browserSession: null, override: null },
|
|
170
223
|
};
|
|
224
|
+
// Record ownership only here, where this module created the dir. Disposal
|
|
225
|
+
// deletes solely what this map holds, so a caller-supplied path is never
|
|
226
|
+
// a deletion target.
|
|
227
|
+
if (profileWarmupDir) ownedWarmupDirs.set(handle, profileWarmupDir);
|
|
228
|
+
return handle;
|
|
171
229
|
}
|
|
172
230
|
if (kind.kind === "connected") {
|
|
173
231
|
const cdpUrl = kind.cdpUrl.replace(/\/+$/, "");
|
|
@@ -418,6 +476,35 @@ export async function releaseBrowser(handle: BrowserHandle, opts: { kill: boolea
|
|
|
418
476
|
}
|
|
419
477
|
}
|
|
420
478
|
|
|
479
|
+
/**
|
|
480
|
+
* Delete the warm-up dir this module created for `handle`, if any.
|
|
481
|
+
*
|
|
482
|
+
* The path comes from `ownedWarmupDirs`, never from the caller, so recursive deletion
|
|
483
|
+
* can only ever target a dir this registry made. The entry is consumed first, making a
|
|
484
|
+
* second call a no-op even if disposal runs twice.
|
|
485
|
+
*
|
|
486
|
+
* Best-effort: a leftover temp dir is preferable to a teardown that throws.
|
|
487
|
+
*/
|
|
488
|
+
function removeOwnedWarmupDir(handle: BrowserHandle): void {
|
|
489
|
+
const dir = ownedWarmupDirs.get(handle);
|
|
490
|
+
if (dir === undefined) return;
|
|
491
|
+
ownedWarmupDirs.delete(handle);
|
|
492
|
+
removeWarmupDir(dir);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Delete a dir this module created. Callers must have proven ownership first. */
|
|
496
|
+
function removeWarmupDir(dir: string | undefined): void {
|
|
497
|
+
if (!dir) return;
|
|
498
|
+
try {
|
|
499
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
500
|
+
} catch (err) {
|
|
501
|
+
logger.debug("Failed to remove Chrome profile warm-up dir", {
|
|
502
|
+
dir,
|
|
503
|
+
error: (err as Error).message,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
421
508
|
async function disposeBrowserHandle(handle: BrowserHandle, opts: { kill: boolean }): Promise<void> {
|
|
422
509
|
if (handle.kind.kind === "headless") {
|
|
423
510
|
// Capture the launched Chrome process before close() so a forced (signal-path)
|
|
@@ -436,6 +523,9 @@ async function disposeBrowserHandle(handle: BrowserHandle, opts: { kill: boolean
|
|
|
436
523
|
}
|
|
437
524
|
}
|
|
438
525
|
if (opts.kill && proc?.pid !== undefined) await gracefulKillTreeOnce(proc.pid);
|
|
526
|
+
// Chrome owns files under the warm-up dir until the process is gone, so this
|
|
527
|
+
// runs after close()/kill rather than alongside them.
|
|
528
|
+
removeOwnedWarmupDir(handle);
|
|
439
529
|
return;
|
|
440
530
|
}
|
|
441
531
|
if (handle.kind.kind === "connected") {
|
package/src/tools/cron.ts
CHANGED
|
@@ -453,13 +453,15 @@ function deliverCronFire(record: CronScheduleRecord): Promise<void> | undefined
|
|
|
453
453
|
cron_expression: record.snapshot.cron_expression,
|
|
454
454
|
recurring: record.snapshot.recurring,
|
|
455
455
|
};
|
|
456
|
-
|
|
456
|
+
if (!record.session.sendCustomMessage) {
|
|
457
|
+
// The custom-message router is the only delivery path; treating its absence
|
|
458
|
+
// as a successful fire would silently retire a one-shot schedule.
|
|
459
|
+
return Promise.reject(new Error("Cron delivery requires a session custom-message router."));
|
|
460
|
+
}
|
|
461
|
+
return record.session.sendCustomMessage(
|
|
457
462
|
{ customType: "cron-fire", content, display: false, attribution: "agent", details },
|
|
458
463
|
{ triggerTurn: true, deliverAs: "followUp" },
|
|
459
464
|
);
|
|
460
|
-
if (sendPromise) return sendPromise;
|
|
461
|
-
record.session.steer?.({ customType: "cron-fire", content, details });
|
|
462
|
-
return undefined;
|
|
463
465
|
}
|
|
464
466
|
|
|
465
467
|
function scheduleRecord(ownerId: string | undefined, state: OwnerScheduleState, record: CronScheduleRecord): void {
|
|
@@ -39,6 +39,7 @@ function makeSession(overrides: Record<string, unknown> = {}): any {
|
|
|
39
39
|
"calc.enabled": true,
|
|
40
40
|
"skill.enabled": true,
|
|
41
41
|
"browser.enabled": true,
|
|
42
|
+
"browser.backend": "native",
|
|
42
43
|
"computer.enabled": true,
|
|
43
44
|
"checkpoint.enabled": true,
|
|
44
45
|
"irc.enabled": true,
|
|
@@ -371,6 +372,20 @@ describe("tool descriptor compatibility gate", () => {
|
|
|
371
372
|
}
|
|
372
373
|
});
|
|
373
374
|
|
|
375
|
+
test("hides the built-in browser tool when Aside is the selected backend", () => {
|
|
376
|
+
const context = availabilityContext();
|
|
377
|
+
|
|
378
|
+
expect(BUILTIN_TOOL_DESCRIPTORS.browser.isAvailable(makeSession({ "browser.backend": "aside" }), context)).toBe(
|
|
379
|
+
false,
|
|
380
|
+
);
|
|
381
|
+
expect(BUILTIN_TOOL_DESCRIPTORS.browser.isAvailable(makeSession({ "browser.backend": "native" }), context)).toBe(
|
|
382
|
+
true,
|
|
383
|
+
);
|
|
384
|
+
expect(BUILTIN_TOOL_DESCRIPTORS.browser.isAvailable(makeSession({ "browser.backend": undefined }), context)).toBe(
|
|
385
|
+
true,
|
|
386
|
+
);
|
|
387
|
+
});
|
|
388
|
+
|
|
374
389
|
test("descriptor creation is side-effect free; materialization registers cleanup exactly once", () => {
|
|
375
390
|
const cleanupCalls: Array<() => void> = [];
|
|
376
391
|
let constructed = 0;
|
package/src/tools/descriptors.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentTool } from "@gajae-code/agent-core";
|
|
2
2
|
import type { RawArgumentValidationResult, TSchema } from "@gajae-code/ai/types";
|
|
3
3
|
import { $which } from "@gajae-code/utils";
|
|
4
|
+
import { resolveBrowserBackend } from "../browser-backend";
|
|
4
5
|
import type { ToolFactory, ToolSession } from ".";
|
|
5
6
|
import { selectAskParameters } from "./ask-contract";
|
|
6
7
|
import { isComputerCallable, isComputerLoadablePlatform } from "./computer-policy";
|
|
@@ -260,7 +261,10 @@ function availableFor(name: string, session: ToolSession, context = defaultAvail
|
|
|
260
261
|
if (name === "search_tool_bm25") return context.discoveryActive ?? resolveDiscoveryActive(session);
|
|
261
262
|
if (name === "calc") return Boolean(session.settings.get("calc.enabled"));
|
|
262
263
|
if (name === "skill" || name === "skill_discovery") return Boolean(session.settings.get("skill.enabled"));
|
|
263
|
-
if (name === "browser")
|
|
264
|
+
if (name === "browser")
|
|
265
|
+
return (
|
|
266
|
+
Boolean(session.settings.get("browser.enabled")) && resolveBrowserBackend(session.settings).exposesBuiltinTool
|
|
267
|
+
);
|
|
264
268
|
if (name === "computer") return isComputerCallable(session);
|
|
265
269
|
if (name === "checkpoint" || name === "rewind")
|
|
266
270
|
return Boolean(session.settings.get("checkpoint.enabled")) && (session.taskDepth ?? 0) === 0;
|
package/src/tools/gh.ts
CHANGED
|
@@ -2806,6 +2806,14 @@ export interface PrDiffFile {
|
|
|
2806
2806
|
changeType: "modified" | "added" | "deleted" | "renamed" | "binary";
|
|
2807
2807
|
/** Pre-image path for renames/deletes; same as `path` otherwise. */
|
|
2808
2808
|
oldPath?: string;
|
|
2809
|
+
/**
|
|
2810
|
+
* Set when `path` kept its C-style escaped source form because the quoted
|
|
2811
|
+
* token held invalid UTF-8 or a malformed escape; distinguishes it from a
|
|
2812
|
+
* valid path whose decoded text looks identical.
|
|
2813
|
+
*/
|
|
2814
|
+
pathEscaped?: boolean;
|
|
2815
|
+
/** Same as {@link PrDiffFile.pathEscaped}, for `oldPath`. */
|
|
2816
|
+
oldPathEscaped?: boolean;
|
|
2809
2817
|
/** Byte offset of the section's `diff --git` line in the unified diff. */
|
|
2810
2818
|
startOffset: number;
|
|
2811
2819
|
/** Byte offset of the next section (or end-of-text). */
|
|
@@ -2861,6 +2869,8 @@ export function parsePrUnifiedDiff(text: string): PrDiffPayload {
|
|
|
2861
2869
|
interface ParsedDiffHeaderToken {
|
|
2862
2870
|
value: string;
|
|
2863
2871
|
nextIndex: number;
|
|
2872
|
+
/** Set when the token kept its C-style escaped source form. */
|
|
2873
|
+
escaped?: boolean;
|
|
2864
2874
|
}
|
|
2865
2875
|
|
|
2866
2876
|
function skipDiffHeaderSpaces(text: string, index: number): number {
|
|
@@ -2869,63 +2879,87 @@ function skipDiffHeaderSpaces(text: string, index: number): number {
|
|
|
2869
2879
|
return i;
|
|
2870
2880
|
}
|
|
2871
2881
|
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2882
|
+
interface ParsedDiffQuotedEscape {
|
|
2883
|
+
byte: number;
|
|
2884
|
+
nextIndex: number;
|
|
2885
|
+
}
|
|
2875
2886
|
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
while (end < text.length && end < slashIndex + 4) {
|
|
2879
|
-
const digit = text.charAt(end);
|
|
2880
|
-
if (digit < "0" || digit > "7") break;
|
|
2881
|
-
end += 1;
|
|
2882
|
-
}
|
|
2883
|
-
return {
|
|
2884
|
-
value: String.fromCharCode(Number.parseInt(text.slice(slashIndex + 1, end), 8)),
|
|
2885
|
-
nextIndex: end,
|
|
2886
|
-
};
|
|
2887
|
-
}
|
|
2887
|
+
const DIFF_PATH_ENCODER = new TextEncoder();
|
|
2888
|
+
const DIFF_PATH_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
2888
2889
|
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2890
|
+
const DIFF_NAMED_ESCAPE_BYTES: Record<string, number> = {
|
|
2891
|
+
a: 0x07,
|
|
2892
|
+
b: 0x08,
|
|
2893
|
+
f: 0x0c,
|
|
2894
|
+
n: 0x0a,
|
|
2895
|
+
r: 0x0d,
|
|
2896
|
+
t: 0x09,
|
|
2897
|
+
v: 0x0b,
|
|
2898
|
+
"\\": 0x5c,
|
|
2899
|
+
'"': 0x22,
|
|
2900
|
+
};
|
|
2901
|
+
|
|
2902
|
+
/**
|
|
2903
|
+
* Git quotes paths C-style: its named escapes plus exactly three octal digits
|
|
2904
|
+
* per byte (leading digit 0-3), so a multi-byte UTF-8 character arrives as
|
|
2905
|
+
* consecutive octal escapes. Anything else is malformed for this grammar.
|
|
2906
|
+
*/
|
|
2907
|
+
function parseDiffQuotedEscape(text: string, slashIndex: number): ParsedDiffQuotedEscape | undefined {
|
|
2908
|
+
const first = text.charAt(slashIndex + 1);
|
|
2909
|
+
if (first >= "0" && first <= "3") {
|
|
2910
|
+
const second = text.charAt(slashIndex + 2);
|
|
2911
|
+
const third = text.charAt(slashIndex + 3);
|
|
2912
|
+
if (second < "0" || second > "7" || third < "0" || third > "7") return undefined;
|
|
2913
|
+
return { byte: Number.parseInt(text.slice(slashIndex + 1, slashIndex + 4), 8), nextIndex: slashIndex + 4 };
|
|
2909
2914
|
}
|
|
2915
|
+
const named = DIFF_NAMED_ESCAPE_BYTES[first];
|
|
2916
|
+
if (named === undefined) return undefined;
|
|
2917
|
+
return { byte: named, nextIndex: slashIndex + 2 };
|
|
2910
2918
|
}
|
|
2911
2919
|
|
|
2920
|
+
/**
|
|
2921
|
+
* Escaped bytes are decoded as strict UTF-8. A token holding invalid UTF-8 or
|
|
2922
|
+
* a malformed escape keeps its escaped source form, so distinct non-UTF-8
|
|
2923
|
+
* byte sequences never collapse into one replacement-character path.
|
|
2924
|
+
*/
|
|
2912
2925
|
function parseDiffQuotedToken(text: string, startIndex: number): ParsedDiffHeaderToken | undefined {
|
|
2913
2926
|
if (text.charAt(startIndex) !== '"') return undefined;
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2927
|
+
const bytes: number[] = [];
|
|
2928
|
+
let malformed = false;
|
|
2929
|
+
for (let i = startIndex + 1; i < text.length; ) {
|
|
2930
|
+
const codePoint = text.codePointAt(i) as number;
|
|
2931
|
+
const ch = String.fromCodePoint(codePoint);
|
|
2932
|
+
if (ch === '"') {
|
|
2933
|
+
if (!malformed) {
|
|
2934
|
+
try {
|
|
2935
|
+
return { value: DIFF_PATH_DECODER.decode(Uint8Array.from(bytes)), nextIndex: i + 1 };
|
|
2936
|
+
} catch {}
|
|
2937
|
+
}
|
|
2938
|
+
return { value: text.slice(startIndex + 1, i), nextIndex: i + 1, escaped: true };
|
|
2939
|
+
}
|
|
2918
2940
|
if (ch !== "\\") {
|
|
2919
|
-
|
|
2941
|
+
bytes.push(...DIFF_PATH_ENCODER.encode(ch));
|
|
2942
|
+
i += ch.length;
|
|
2920
2943
|
continue;
|
|
2921
2944
|
}
|
|
2922
2945
|
const escaped = parseDiffQuotedEscape(text, i);
|
|
2923
|
-
|
|
2924
|
-
|
|
2946
|
+
if (escaped === undefined) {
|
|
2947
|
+
malformed = true;
|
|
2948
|
+
i += 2;
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
bytes.push(escaped.byte);
|
|
2952
|
+
i = escaped.nextIndex;
|
|
2925
2953
|
}
|
|
2926
2954
|
return undefined;
|
|
2927
2955
|
}
|
|
2928
2956
|
|
|
2957
|
+
function parseDiffPathLine(line: string, prefix: string): { value: string; escaped?: boolean } {
|
|
2958
|
+
const rest = line.slice(prefix.length);
|
|
2959
|
+
const token = parseDiffQuotedToken(rest, 0);
|
|
2960
|
+
return token ? { value: token.value, escaped: token.escaped } : { value: rest };
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2929
2963
|
function parseDiffHeaderToken(text: string, startIndex: number): ParsedDiffHeaderToken | undefined {
|
|
2930
2964
|
const start = skipDiffHeaderSpaces(text, startIndex);
|
|
2931
2965
|
if (start >= text.length) return undefined;
|
|
@@ -2940,7 +2974,14 @@ function stripPrDiffPathPrefix(value: string, prefix: "a/" | "b/"): string | und
|
|
|
2940
2974
|
return value.startsWith(prefix) ? value.slice(prefix.length) : undefined;
|
|
2941
2975
|
}
|
|
2942
2976
|
|
|
2943
|
-
|
|
2977
|
+
interface PrDiffHeaderPaths {
|
|
2978
|
+
oldPath?: string;
|
|
2979
|
+
newPath?: string;
|
|
2980
|
+
oldPathEscaped?: boolean;
|
|
2981
|
+
newPathEscaped?: boolean;
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
function parsePrDiffHeaderPaths(header: string): PrDiffHeaderPaths {
|
|
2944
2985
|
const trail = header.slice("diff --git ".length);
|
|
2945
2986
|
if (trail.startsWith('"')) {
|
|
2946
2987
|
const oldToken = parseDiffQuotedToken(trail, 0);
|
|
@@ -2950,6 +2991,8 @@ function parsePrDiffHeaderPaths(header: string): { oldPath?: string; newPath?: s
|
|
|
2950
2991
|
return {
|
|
2951
2992
|
oldPath: stripPrDiffPathPrefix(oldToken.value, "a/"),
|
|
2952
2993
|
newPath: stripPrDiffPathPrefix(newToken.value, "b/"),
|
|
2994
|
+
oldPathEscaped: oldToken.escaped,
|
|
2995
|
+
newPathEscaped: newToken.escaped,
|
|
2953
2996
|
};
|
|
2954
2997
|
}
|
|
2955
2998
|
|
|
@@ -2980,6 +3023,8 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
2980
3023
|
const headerPaths = parsePrDiffHeaderPaths(header);
|
|
2981
3024
|
let oldPath = headerPaths.oldPath;
|
|
2982
3025
|
let newPath = headerPaths.newPath;
|
|
3026
|
+
let oldPathEscaped = headerPaths.oldPathEscaped;
|
|
3027
|
+
let newPathEscaped = headerPaths.newPathEscaped;
|
|
2983
3028
|
|
|
2984
3029
|
let changeType: PrDiffFile["changeType"] = "modified";
|
|
2985
3030
|
let isBinary = false;
|
|
@@ -2999,11 +3044,11 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
2999
3044
|
}
|
|
3000
3045
|
if (line.startsWith("rename from ")) {
|
|
3001
3046
|
changeType = "renamed";
|
|
3002
|
-
oldPath = line
|
|
3047
|
+
({ value: oldPath, escaped: oldPathEscaped } = parseDiffPathLine(line, "rename from "));
|
|
3003
3048
|
continue;
|
|
3004
3049
|
}
|
|
3005
3050
|
if (line.startsWith("rename to ")) {
|
|
3006
|
-
newPath = line
|
|
3051
|
+
({ value: newPath, escaped: newPathEscaped } = parseDiffPathLine(line, "rename to "));
|
|
3007
3052
|
continue;
|
|
3008
3053
|
}
|
|
3009
3054
|
if (line.startsWith("Binary files ") && line.endsWith(" differ")) {
|
|
@@ -3028,8 +3073,15 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
3028
3073
|
deletions = 0;
|
|
3029
3074
|
}
|
|
3030
3075
|
|
|
3031
|
-
const
|
|
3032
|
-
|
|
3076
|
+
const preferOld = changeType === "deleted";
|
|
3077
|
+
const displayPath = preferOld ? (oldPath ?? newPath ?? "(unknown)") : (newPath ?? oldPath ?? "(unknown)");
|
|
3078
|
+
const displayEscaped = preferOld
|
|
3079
|
+
? oldPath !== undefined
|
|
3080
|
+
? oldPathEscaped
|
|
3081
|
+
: newPathEscaped
|
|
3082
|
+
: newPath !== undefined
|
|
3083
|
+
? newPathEscaped
|
|
3084
|
+
: oldPathEscaped;
|
|
3033
3085
|
const file: PrDiffFile = {
|
|
3034
3086
|
path: displayPath,
|
|
3035
3087
|
additions,
|
|
@@ -3038,8 +3090,12 @@ function parsePrDiffSection(section: string, startOffset: number, endOffset: num
|
|
|
3038
3090
|
startOffset,
|
|
3039
3091
|
endOffset,
|
|
3040
3092
|
};
|
|
3041
|
-
if (
|
|
3093
|
+
if (displayEscaped) {
|
|
3094
|
+
file.pathEscaped = true;
|
|
3095
|
+
}
|
|
3096
|
+
if (oldPath && (oldPath !== displayPath || oldPathEscaped !== displayEscaped)) {
|
|
3042
3097
|
file.oldPath = oldPath;
|
|
3098
|
+
if (oldPathEscaped) file.oldPathEscaped = true;
|
|
3043
3099
|
}
|
|
3044
3100
|
return file;
|
|
3045
3101
|
}
|
|
@@ -103,7 +103,7 @@ export function openDb(): Database | null {
|
|
|
103
103
|
// than running an in-place ALTER dance.
|
|
104
104
|
const userVersion = (db.prepare("PRAGMA user_version").get() as { user_version?: number } | undefined)
|
|
105
105
|
?.user_version;
|
|
106
|
-
if (userVersion !== undefined && userVersion <
|
|
106
|
+
if (userVersion !== undefined && userVersion < 4) {
|
|
107
107
|
db.run("DROP TABLE IF EXISTS github_view_cache");
|
|
108
108
|
}
|
|
109
109
|
db.run(`
|
|
@@ -120,7 +120,7 @@ export function openDb(): Database | null {
|
|
|
120
120
|
PRIMARY KEY (auth_key, repo, kind, number, include_comments)
|
|
121
121
|
);
|
|
122
122
|
CREATE INDEX IF NOT EXISTS idx_github_view_cache_fetched ON github_view_cache(fetched_at);
|
|
123
|
-
PRAGMA user_version =
|
|
123
|
+
PRAGMA user_version = 4;
|
|
124
124
|
`);
|
|
125
125
|
protectDbFiles(dbPath);
|
|
126
126
|
cachedDb = db;
|
package/src/tools/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentTelemetryConfig, AgentTool } from "@gajae-code/agent-core";
|
|
2
2
|
import type { Model, ServiceTier, ToolChoice } from "@gajae-code/ai/core";
|
|
3
3
|
import { $env, logger } from "@gajae-code/utils";
|
|
4
|
-
import type { AsyncJobManager } from "../async";
|
|
4
|
+
import type { AsyncJobManager, FoldReason } from "../async";
|
|
5
5
|
import type { PromptTemplate } from "../config/prompt-templates";
|
|
6
6
|
import type { Settings } from "../config/settings";
|
|
7
7
|
import type { Skill } from "../extensibility/skills";
|
|
@@ -240,12 +240,14 @@ export interface ToolSession {
|
|
|
240
240
|
registerForegroundFoldParticipant?: (adapter: FoldAdapter) => () => void;
|
|
241
241
|
/** Whether a foreground wait is currently foldable into a background job. */
|
|
242
242
|
hasForegroundBashBackgroundRequestHandler?: () => boolean;
|
|
243
|
-
/** Request that
|
|
244
|
-
requestForegroundBashBackground?: () => Promise<boolean>;
|
|
243
|
+
/** Request that a foreground wait fold into a background job, if supported. `reason` names the trigger; `adapter` targets a specific wait instead of the newest registration. */
|
|
244
|
+
requestForegroundBashBackground?: (reason?: FoldReason, adapter?: FoldAdapter) => Promise<boolean>;
|
|
245
|
+
/** Current tool interrupt policy; `finish_tools` lets the running tool finish, so a steer must not fold it. */
|
|
246
|
+
getToolInterruptPolicy?: () => "abort_tools" | "finish_tools";
|
|
245
247
|
|
|
246
248
|
/** Get the session-owned or inherited async job manager. */
|
|
247
249
|
getAsyncJobManager?: () => AsyncJobManager | undefined;
|
|
248
|
-
/** Resolves
|
|
250
|
+
/** Resolves on a steer admitted after the wait starts, or when `signal` aborts, without consuming the steer. */
|
|
249
251
|
waitForUserSteering?: (signal: AbortSignal) => Promise<void>;
|
|
250
252
|
/** Get session ID */
|
|
251
253
|
getSessionId?: () => string | null;
|
|
@@ -271,6 +273,10 @@ export interface ToolSession {
|
|
|
271
273
|
bashAllowedPrefixes?: string[];
|
|
272
274
|
/** Restriction policy for sessions that deliberately expose a narrow bash surface. */
|
|
273
275
|
bashRestrictionProfile?: BashRestrictionProfile;
|
|
276
|
+
/** Process-local master capability injected into direct Bash children only. */
|
|
277
|
+
getMasterBashCapability?: () => string | undefined;
|
|
278
|
+
/** Stable master lineage identity used across session switches and branches. */
|
|
279
|
+
getMasterOwnerSessionId?: () => string | undefined;
|
|
274
280
|
/** Optional per-session allowlist for tools exposed through search_tool_bm25. */
|
|
275
281
|
discoverableToolAllowedNames?: readonly string[];
|
|
276
282
|
/** Throw instead of warn when toolNames contains an unknown name. */
|
|
@@ -375,8 +381,6 @@ export interface ToolSession {
|
|
|
375
381
|
buildToolChoice?(toolName: string): ToolChoice | undefined;
|
|
376
382
|
/** Build a named tool-choice decision, preserving whether exact named forcing survived capability degradation. */
|
|
377
383
|
buildToolChoiceResult?(toolName: string): import("../utils/tool-choice").NamedToolChoiceResult;
|
|
378
|
-
/** Steer a hidden custom message into the conversation (e.g. a preview reminder). */
|
|
379
|
-
steer?(message: { customType: string; content: string; details?: unknown }): void;
|
|
380
384
|
/** Peek the currently in-flight tool-choice queue directive's invocation handler. Used by the `resolve` tool to dispatch to the pending action. */
|
|
381
385
|
peekQueueInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
|
|
382
386
|
/** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
|
package/src/tools/job.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Component } from "@gajae-code/tui";
|
|
|
3
3
|
import { Text } from "@gajae-code/tui";
|
|
4
4
|
import { prompt } from "@gajae-code/utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
6
|
-
import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElapsedMs } from "../async";
|
|
6
|
+
import { type AsyncJob, AsyncJobManager, type FoldReason, isBackgroundJobSupportEnabled, jobElapsedMs } from "../async";
|
|
7
7
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
8
|
import type { Theme } from "../modes/theme/theme";
|
|
9
9
|
import jobDescription from "../prompts/tools/job.md" with { type: "text" };
|
|
@@ -50,6 +50,8 @@ interface JobSnapshot {
|
|
|
50
50
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
51
51
|
label: string;
|
|
52
52
|
durationMs: number;
|
|
53
|
+
/** Present when the job was folded out of a foreground wait. */
|
|
54
|
+
foldReason?: FoldReason;
|
|
53
55
|
resultText?: string;
|
|
54
56
|
errorText?: string;
|
|
55
57
|
}
|
|
@@ -344,6 +346,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
|
|
|
344
346
|
status: latest.status as JobSnapshot["status"],
|
|
345
347
|
label: latest.label,
|
|
346
348
|
durationMs: jobElapsedMs(latest, now),
|
|
349
|
+
...(current?.metadata?.foldReason ? { foldReason: current.metadata.foldReason } : {}),
|
|
347
350
|
...(latest.resultText ? { resultText: latest.resultText } : {}),
|
|
348
351
|
...(latest.errorText ? { errorText: latest.errorText } : {}),
|
|
349
352
|
};
|
package/src/tools/monitor.ts
CHANGED
|
@@ -229,19 +229,22 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
|
|
|
229
229
|
);
|
|
230
230
|
pendingNotifications = MAX_PENDING_MONITOR_NOTIFICATIONS;
|
|
231
231
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
232
|
+
if (!this.session.sendCustomMessage) {
|
|
233
|
+
logger.warn("Monitor task-notification dropped: session has no custom-message router", {
|
|
234
|
+
taskId: jobId,
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
void this.session
|
|
239
|
+
.sendCustomMessage(
|
|
240
|
+
{ customType: "task-notification", content, display: false, attribution: "agent", details },
|
|
241
|
+
{ triggerTurn: true, deliverAs: "followUp" },
|
|
242
|
+
)
|
|
243
|
+
.catch(error => {
|
|
238
244
|
logger.warn("Monitor task-notification delivery failed", {
|
|
239
245
|
error: error instanceof Error ? error.message : String(error),
|
|
240
246
|
});
|
|
241
247
|
});
|
|
242
|
-
} else {
|
|
243
|
-
this.session.steer?.({ customType: "task-notification", content, details });
|
|
244
|
-
}
|
|
245
248
|
};
|
|
246
249
|
const schedulePersistentNotification = (line: string) => {
|
|
247
250
|
latestLine = line;
|