@gajae-code/coding-agent 0.16.7 → 0.17.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 +180 -1
- package/dist/types/capability/fs.d.ts +37 -4
- package/dist/types/capability/index.d.ts +19 -0
- package/dist/types/capability/types.d.ts +9 -0
- package/dist/types/cli/doctor/args.d.ts +36 -0
- package/dist/types/cli/doctor/checks.d.ts +18 -0
- package/dist/types/cli/doctor/config-repairs.d.ts +38 -0
- package/dist/types/cli/doctor/context.d.ts +89 -0
- package/dist/types/cli/doctor/files.d.ts +27 -0
- package/dist/types/cli/doctor/ids.d.ts +20 -0
- package/dist/types/cli/doctor/install-repairs.d.ts +94 -0
- package/dist/types/cli/doctor/journal.d.ts +43 -0
- package/dist/types/cli/doctor/managed-link.d.ts +39 -0
- package/dist/types/cli/doctor/native-probe.d.ts +2 -0
- package/dist/types/cli/doctor/permission-repairs.d.ts +25 -0
- package/dist/types/cli/doctor/plugin-quarantine.d.ts +77 -0
- package/dist/types/cli/doctor/plugin-restore.d.ts +40 -0
- package/dist/types/cli/doctor/plugin-targets.d.ts +11 -0
- package/dist/types/cli/doctor/probe-client.d.ts +9 -0
- package/dist/types/cli/doctor/probe-types.d.ts +18 -0
- package/dist/types/cli/doctor/probe.d.ts +3 -0
- package/dist/types/cli/doctor/projection-probe.d.ts +2 -0
- package/dist/types/cli/doctor/repairs.d.ts +28 -0
- package/dist/types/cli/doctor/report.d.ts +27 -0
- package/dist/types/cli/doctor/runner.d.ts +4 -0
- package/dist/types/cli/doctor/safety.d.ts +10 -0
- package/dist/types/cli/doctor/service-repairs.d.ts +8 -0
- package/dist/types/cli/doctor/service-targets.d.ts +41 -0
- package/dist/types/cli/doctor/types.d.ts +145 -0
- package/dist/types/cli/doctor-cli.d.ts +7 -0
- package/dist/types/cli/doctor-supervisor.d.ts +121 -0
- package/dist/types/cli/doctor-worker.d.ts +29 -0
- package/dist/types/cli/install-activation.d.ts +158 -0
- package/dist/types/cli/macos-community-app.d.ts +41 -0
- package/dist/types/cli/mcp-cli.d.ts +0 -11
- package/dist/types/cli/update-cli.d.ts +41 -4
- package/dist/types/cli-main.d.ts +174 -0
- package/dist/types/cli.d.ts +1 -171
- package/dist/types/commands/doctor.d.ts +7 -0
- package/dist/types/commands/sdk.d.ts +17 -8
- package/dist/types/config/autorouting-tier-map.d.ts +19 -0
- package/dist/types/config/file-lock.d.ts +27 -3
- package/dist/types/config/model-preset-registry.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +12 -5
- package/dist/types/config/model-resolver.d.ts +16 -3
- package/dist/types/config/models-config-schema.d.ts +2 -0
- package/dist/types/config/settings-schema.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-gate-codec.d.ts +1 -1
- package/dist/types/coordinator-mcp/session-reaper.d.ts +15 -5
- package/dist/types/cursor.d.ts +9 -9
- package/dist/types/discovery/agents-md.d.ts +6 -1
- package/dist/types/discovery/helpers.d.ts +36 -7
- package/dist/types/discovery/index.d.ts +1 -1
- package/dist/types/discovery/mcp-json.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +22 -1
- package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +14 -1
- package/dist/types/extensibility/gjc-plugins/paths.d.ts +2 -1
- package/dist/types/extensibility/gjc-plugins/types.d.ts +25 -0
- package/dist/types/extensibility/plugins/manager.d.ts +33 -4
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +6 -0
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +186 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +27 -0
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +16 -1
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +16 -3
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +6 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +53 -0
- package/dist/types/gjc-runtime/session-layout.d.ts +8 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +15 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +6 -0
- package/dist/types/main.d.ts +19 -1
- package/dist/types/modes/acp/acp-agent.d.ts +40 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/runtime-mcp/config-writer.d.ts +47 -0
- package/dist/types/runtime-mcp/policy-json-edit.d.ts +60 -0
- package/dist/types/runtime-mcp/startup-policy.d.ts +4 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -2
- package/dist/types/sdk/acp/adapter.d.ts +5 -1
- package/dist/types/sdk/broker/broker.d.ts +51 -0
- package/dist/types/sdk/broker/daemon-entry.d.ts +39 -0
- package/dist/types/sdk/broker/discovery.d.ts +38 -0
- package/dist/types/sdk/broker/doctor-restart.d.ts +79 -0
- package/dist/types/sdk/broker/endpoint-authority.d.ts +7 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +2 -0
- package/dist/types/sdk/broker/process-incarnation.d.ts +50 -0
- package/dist/types/sdk/broker/runtime.d.ts +26 -0
- package/dist/types/sdk/broker/session-index.d.ts +13 -4
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +14 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +16 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +101 -6
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +16 -0
- package/dist/types/sdk/bus/daemon-startup-exclusion.d.ts +43 -0
- package/dist/types/sdk/bus/doctor-daemon-restart-client.d.ts +51 -0
- package/dist/types/sdk/bus/doctor-daemon-restart.d.ts +39 -0
- package/dist/types/sdk/bus/index.d.ts +2 -0
- package/dist/types/sdk/bus/notification-service.d.ts +3 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -0
- package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +1 -0
- package/dist/types/sdk/bus/telegram-daemon.d.ts +36 -5
- package/dist/types/sdk/cli/session-cli.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +4 -0
- package/dist/types/sdk/host/surface-policy.d.ts +3 -1
- package/dist/types/sdk/lifecycle/service.d.ts +44 -10
- package/dist/types/sdk/prompt-deadline-lease.d.ts +6 -3
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -2
- package/dist/types/sdk/prompt-failure.d.ts +47 -0
- package/dist/types/sdk/prompt-status.d.ts +19 -0
- package/dist/types/sdk/router/session-router.d.ts +3 -4
- package/dist/types/sdk/service-artifact-paths.d.ts +16 -0
- package/dist/types/sdk/session-authority.d.ts +9 -0
- package/dist/types/sdk/session.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +1 -1
- package/dist/types/session/fallback-chain-controller.d.ts +3 -6
- package/dist/types/session/internal/managed-session-scope.d.ts +44 -0
- package/dist/types/session/primary-control-surface.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +6 -0
- package/dist/types/tools/ask.d.ts +25 -0
- package/dist/types/tools/image-gen.d.ts +0 -4
- package/dist/types/tools/skill-discovery.d.ts +7 -5
- package/dist/types/tools/ultragoal-ask-guard.d.ts +7 -4
- package/dist/types/utils/changelog.d.ts +0 -9
- package/dist/types/utils/mupdf-wasm.d.ts +1 -0
- package/package.json +9 -8
- package/scripts/compile-args.ts +8 -4
- package/src/capability/fs.ts +395 -17
- package/src/capability/index.ts +273 -10
- package/src/capability/types.ts +9 -0
- package/src/cli/customize-doctor.ts +18 -12
- package/src/cli/doctor/args.ts +236 -0
- package/src/cli/doctor/checks.ts +431 -0
- package/src/cli/doctor/config-repairs.ts +517 -0
- package/src/cli/doctor/context.ts +149 -0
- package/src/cli/doctor/files.ts +149 -0
- package/src/cli/doctor/ids.ts +115 -0
- package/src/cli/doctor/install-repairs.ts +335 -0
- package/src/cli/doctor/journal.ts +423 -0
- package/src/cli/doctor/managed-link.ts +514 -0
- package/src/cli/doctor/native-probe.ts +13 -0
- package/src/cli/doctor/permission-repairs.ts +242 -0
- package/src/cli/doctor/plugin-quarantine.ts +377 -0
- package/src/cli/doctor/plugin-restore.ts +59 -0
- package/src/cli/doctor/plugin-targets.ts +373 -0
- package/src/cli/doctor/probe-client.ts +195 -0
- package/src/cli/doctor/probe-types.ts +21 -0
- package/src/cli/doctor/probe.ts +42 -0
- package/src/cli/doctor/projection-probe.ts +249 -0
- package/src/cli/doctor/repairs.ts +1157 -0
- package/src/cli/doctor/report.ts +176 -0
- package/src/cli/doctor/runner.ts +342 -0
- package/src/cli/doctor/safety.ts +40 -0
- package/src/cli/doctor/service-repairs.ts +452 -0
- package/src/cli/doctor/service-targets.ts +274 -0
- package/src/cli/doctor/types.ts +164 -0
- package/src/cli/doctor-cli.ts +62 -0
- package/src/cli/doctor-supervisor.ts +571 -0
- package/src/cli/doctor-worker.ts +181 -0
- package/src/cli/install-activation.ts +666 -0
- package/src/cli/macos-community-app.ts +1449 -0
- package/src/cli/mcp-cli.ts +11 -21
- package/src/cli/nofile-limit.ts +5 -1
- package/src/cli/plugin-cli.ts +6 -1
- package/src/cli/update-cli.ts +362 -264
- package/src/cli-main.ts +517 -0
- package/src/cli.ts +64 -433
- package/src/commands/autoresearch.ts +11 -5
- package/src/commands/completion.ts +1 -1
- package/src/commands/doctor.ts +13 -0
- package/src/commands/sdk.ts +58 -10
- package/src/commands/ultragoal.ts +11 -5
- package/src/config/autorouting-tier-map.ts +7 -0
- package/src/config/file-lock.ts +736 -71
- package/src/config/mcp-schema.json +3 -3
- package/src/config/model-preset-registry.ts +1 -0
- package/src/config/model-registry.ts +80 -27
- package/src/config/model-resolver.ts +33 -4
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +6 -2
- package/src/coordinator-mcp/question-gate-codec.ts +1 -0
- package/src/coordinator-mcp/server.ts +534 -51
- package/src/coordinator-mcp/session-reaper.ts +64 -8
- package/src/cursor.ts +223 -53
- package/src/discovery/agents-md.ts +74 -8
- package/src/discovery/agents.ts +32 -7
- package/src/discovery/builtin.ts +184 -75
- package/src/discovery/claude-plugins.ts +85 -17
- package/src/discovery/cline.ts +38 -10
- package/src/discovery/cursor.ts +19 -6
- package/src/discovery/gemini.ts +62 -22
- package/src/discovery/github.ts +13 -3
- package/src/discovery/helpers.ts +483 -89
- package/src/discovery/index.ts +1 -0
- package/src/discovery/mcp-json.ts +36 -10
- package/src/discovery/opencode.ts +36 -19
- package/src/discovery/ssh.ts +11 -8
- package/src/discovery/windsurf.ts +12 -3
- package/src/exec/bash-executor.ts +10 -3
- package/src/exec/isolated-shell.ts +15 -2
- package/src/extensibility/extensions/loader.ts +88 -26
- package/src/extensibility/gjc-plugins/lifecycle.ts +189 -0
- package/src/extensibility/gjc-plugins/paths.ts +4 -1
- package/src/extensibility/gjc-plugins/types.ts +27 -0
- package/src/extensibility/plugins/manager.ts +200 -22
- package/src/extensibility/plugins/marketplace/cache.ts +55 -2
- package/src/extensibility/plugins/marketplace/manager.ts +1262 -162
- package/src/extensibility/plugins/marketplace/registry.ts +153 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +116 -10
- package/src/extensibility/runtime-skill-discovery.ts +61 -9
- package/src/extensibility/skill-management.ts +7 -1
- package/src/gjc-runtime/autoresearch-runtime.ts +10 -1
- package/src/gjc-runtime/launch-worktree.test.ts +248 -0
- package/src/gjc-runtime/launch-worktree.ts +275 -38
- package/src/gjc-runtime/repository-binding.ts +26 -1
- package/src/gjc-runtime/session-layout.ts +10 -0
- package/src/gjc-runtime/session-state-lock.ts +330 -23
- package/src/gjc-runtime/session-state-sidecar.ts +328 -80
- package/src/gjc-runtime/ultragoal-runtime.ts +10 -1
- package/src/harness-control-plane/session-lease.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +28 -26
- package/src/main.ts +150 -29
- package/src/memories/index.ts +24 -2
- package/src/modes/acp/acp-agent.ts +522 -68
- package/src/modes/components/agent-dashboard.ts +3 -1
- package/src/modes/components/model-selector.ts +28 -19
- package/src/modes/components/plugin-settings.ts +2 -1
- package/src/modes/controllers/selector-controller.ts +15 -9
- package/src/modes/interactive-mode.ts +21 -3
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/move-session.md +4 -0
- package/src/prompts/tools/read.md +2 -1
- package/src/prompts/tools/skill-discovery.md +6 -5
- package/src/prompts/tools/task.md +1 -0
- package/src/runtime-mcp/config-writer.ts +228 -0
- package/src/runtime-mcp/oauth-discovery.ts +114 -14
- package/src/runtime-mcp/policy-json-edit.ts +129 -0
- package/src/runtime-mcp/startup-policy.ts +14 -0
- package/src/runtime-mcp/types.ts +2 -2
- package/src/sdk/acp/adapter.ts +27 -12
- package/src/sdk/broker/broker.ts +273 -44
- package/src/sdk/broker/daemon-entry.ts +119 -0
- package/src/sdk/broker/discovery.ts +173 -20
- package/src/sdk/broker/doctor-restart.ts +206 -0
- package/src/sdk/broker/endpoint-authority.ts +36 -0
- package/src/sdk/broker/ensure.ts +49 -14
- package/src/sdk/broker/lifecycle.ts +174 -42
- package/src/sdk/broker/process-incarnation.ts +86 -2
- package/src/sdk/broker/runtime.ts +145 -21
- package/src/sdk/broker/session-index.ts +136 -22
- package/src/sdk/broker/spawn-substrate.ts +4 -1
- package/src/sdk/broker/transport.ts +5 -0
- package/src/sdk/broker/worktree-occupancy.ts +21 -1
- package/src/sdk/bus/chat-daemon-cli.ts +63 -0
- package/src/sdk/bus/chat-daemon-control.ts +260 -64
- package/src/sdk/bus/chat-daemon-runtime.ts +141 -25
- package/src/sdk/bus/daemon-startup-exclusion.ts +119 -0
- package/src/sdk/bus/doctor-daemon-restart-client.ts +291 -0
- package/src/sdk/bus/doctor-daemon-restart.ts +92 -0
- package/src/sdk/bus/index.ts +252 -51
- package/src/sdk/bus/kind-aware-reconciliation.ts +76 -41
- package/src/sdk/bus/notification-service.ts +21 -3
- package/src/sdk/bus/reconciliation-store.ts +17 -11
- package/src/sdk/bus/telegram-daemon-cli.ts +91 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/bus/telegram-daemon-control.ts +47 -0
- package/src/sdk/bus/telegram-daemon-owner-registry.ts +1 -1
- package/src/sdk/bus/telegram-daemon.ts +178 -13
- package/src/sdk/cli/session-cli.ts +100 -12
- package/src/sdk/host/host.ts +2 -7
- package/src/sdk/host/session-runtime.test.ts +329 -8
- package/src/sdk/host/session-runtime.ts +202 -50
- package/src/sdk/host/surface-policy.ts +8 -1
- package/src/sdk/lifecycle/service.ts +199 -22
- package/src/sdk/mcp/server.ts +1 -1
- package/src/sdk/prompt-deadline-lease.ts +12 -4
- package/src/sdk/prompt-deadline-manager.ts +5 -7
- package/src/sdk/prompt-failure.ts +148 -0
- package/src/sdk/prompt-status.ts +30 -1
- package/src/sdk/prompt-watchdog.ts +11 -3
- package/src/sdk/protocol/operation-inventory.generated.json +18 -0
- package/src/sdk/protocol/operation-registry.ts +20 -8
- package/src/sdk/router/session-router.ts +8 -3
- package/src/sdk/service-artifact-paths.ts +26 -0
- package/src/sdk/session-authority.ts +10 -0
- package/src/sdk/session.ts +361 -194
- package/src/session/agent-session.ts +398 -93
- package/src/session/blob-store.ts +9 -9
- package/src/session/fallback-chain-controller.ts +3 -7
- package/src/session/internal/managed-session-scope.ts +445 -29
- package/src/session/internal/managed-session-storage.ts +38 -6
- package/src/session/primary-control-surface.ts +12 -0
- package/src/session/session-manager.ts +11 -3
- package/src/session/session-storage.ts +1 -1
- package/src/session-import/codex.ts +6 -1
- package/src/session-import/redact.ts +7 -0
- package/src/skill-state/workflow-mutation-guard.ts +645 -39
- package/src/slash-commands/builtin-registry.ts +7 -3
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +36 -3
- package/src/task/types.ts +27 -5
- package/src/tools/ask.ts +114 -34
- package/src/tools/browser/actions.ts +2 -2
- package/src/tools/browser/tab-worker.ts +11 -8
- package/src/tools/descriptors.ts +6 -1
- package/src/tools/fetch.ts +7 -2
- package/src/tools/image-gen.ts +33 -34
- package/src/tools/read.ts +78 -32
- package/src/tools/skill-discovery.ts +17 -7
- package/src/tools/tool-catalog.generated.ts +8 -8
- package/src/tools/ultragoal-ask-guard.ts +16 -26
- package/src/utils/changelog.ts +7 -1
- package/src/utils/git.ts +3 -1
- package/src/utils/markit.ts +24 -2
- package/src/utils/mupdf-wasm.ts +26 -0
- package/src/web/search/providers/openai-compatible.ts +191 -14
package/src/tools/read.ts
CHANGED
|
@@ -479,8 +479,7 @@ function formatSummaryCapFooter(
|
|
|
479
479
|
totalLines: number,
|
|
480
480
|
direction: TruncationDirection = "head",
|
|
481
481
|
): string {
|
|
482
|
-
const
|
|
483
|
-
const directionPart = direction === "head" ? "" : `; retained ${retained} summary units`;
|
|
482
|
+
const directionPart = direction === "head" ? "" : `; truncation: ${direction}`;
|
|
484
483
|
return `[Summary truncated at ${Math.ceil(maxBytes / 1024)} KiB${directionPart}; re-read ${readPath}:1-${totalLines} or ${readPath}:raw for the full source]`;
|
|
485
484
|
}
|
|
486
485
|
const READ_CHUNK_SIZE = 8 * 1024;
|
|
@@ -2498,12 +2497,16 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2498
2497
|
}
|
|
2499
2498
|
}
|
|
2500
2499
|
|
|
2501
|
-
#renderSummary(
|
|
2500
|
+
#renderSummary(
|
|
2501
|
+
summary: SummaryResult,
|
|
2502
|
+
direction: TruncationDirection,
|
|
2503
|
+
): {
|
|
2502
2504
|
text: string;
|
|
2503
2505
|
displayText: string;
|
|
2504
2506
|
elidedSpans: number;
|
|
2505
2507
|
elidedLines: number;
|
|
2506
2508
|
capped: boolean;
|
|
2509
|
+
omissionNotice: string;
|
|
2507
2510
|
} {
|
|
2508
2511
|
const displayMode = resolveFileDisplayMode(this.session);
|
|
2509
2512
|
const shouldAddHashLines = displayMode.hashLines;
|
|
@@ -2561,14 +2564,8 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2561
2564
|
i++;
|
|
2562
2565
|
}
|
|
2563
2566
|
|
|
2564
|
-
const modelParts: string[] = [];
|
|
2565
|
-
const displayParts: string[] = [];
|
|
2566
2567
|
const capBytes = this.session.settings.get("read.summaryMaxBytes") * 1024;
|
|
2567
|
-
|
|
2568
|
-
let elidedSpans = 0;
|
|
2569
|
-
let elidedLines = 0;
|
|
2570
|
-
let capDropped = false;
|
|
2571
|
-
for (const unit of units) {
|
|
2568
|
+
const rendered = units.map(unit => {
|
|
2572
2569
|
let model: string;
|
|
2573
2570
|
let display: string;
|
|
2574
2571
|
let implicitElidedLines = 0;
|
|
@@ -2591,31 +2588,82 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2591
2588
|
model = formatSingleLine(unit.line, unit.text, shouldAddHashLines, shouldAddLineNumbers);
|
|
2592
2589
|
display = unit.text;
|
|
2593
2590
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2591
|
+
return { model, display, implicitElidedLines };
|
|
2592
|
+
});
|
|
2593
|
+
// Prefix sums include each unit's separator. Removing the final separator
|
|
2594
|
+
// gives the exact UTF-8 body size, without charging recovery footers.
|
|
2595
|
+
const bytes = [0];
|
|
2596
|
+
for (const part of rendered) {
|
|
2597
|
+
bytes.push(bytes[bytes.length - 1] + Buffer.byteLength(part.model, "utf-8") + 1);
|
|
2598
|
+
}
|
|
2599
|
+
const omissionMarker = (head: number, tail: number): string => {
|
|
2600
|
+
const first = units[head];
|
|
2601
|
+
const last = units[tail - 1];
|
|
2602
|
+
const start = first.kind === "line" ? first.line : first.startLine;
|
|
2603
|
+
const end = last.kind === "line" ? last.line : last.endLine;
|
|
2604
|
+
return `[… source lines ${start}-${end} omitted …]`;
|
|
2605
|
+
};
|
|
2606
|
+
let head = units.length;
|
|
2607
|
+
let tail = units.length;
|
|
2608
|
+
const capDropped = bytes[units.length] - 1 > capBytes;
|
|
2609
|
+
if (capDropped) {
|
|
2610
|
+
head = 0;
|
|
2611
|
+
const fits = (nextHead: number, nextTail: number): boolean =>
|
|
2612
|
+
bytes[nextHead] +
|
|
2613
|
+
bytes[units.length] -
|
|
2614
|
+
bytes[nextTail] +
|
|
2615
|
+
Buffer.byteLength(omissionMarker(nextHead, nextTail), "utf-8") <=
|
|
2616
|
+
capBytes;
|
|
2617
|
+
let headBlocked = direction === "last";
|
|
2618
|
+
let tailBlocked = direction === "head";
|
|
2619
|
+
// Keep contiguous edge windows: an oversized unit blocks that edge,
|
|
2620
|
+
// never splits an anchor or turns a prefix/suffix into scattered lines.
|
|
2621
|
+
while (head + 1 < tail && (!headBlocked || !tailBlocked)) {
|
|
2622
|
+
const takeHead = !headBlocked && (tailBlocked || bytes[head] <= bytes[units.length] - bytes[tail]);
|
|
2623
|
+
if (takeHead) {
|
|
2624
|
+
if (fits(head + 1, tail)) head++;
|
|
2625
|
+
else headBlocked = true;
|
|
2626
|
+
} else {
|
|
2627
|
+
if (fits(head, tail - 1)) tail--;
|
|
2628
|
+
else tailBlocked = true;
|
|
2629
|
+
}
|
|
2600
2630
|
}
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2631
|
+
}
|
|
2632
|
+
const modelParts: string[] = [];
|
|
2633
|
+
const displayParts: string[] = [];
|
|
2634
|
+
let elidedSpans = 0;
|
|
2635
|
+
let elidedLines = 0;
|
|
2636
|
+
let omissionNotice = "";
|
|
2637
|
+
for (let index = 0; index < units.length; index++) {
|
|
2638
|
+
const unit = units[index];
|
|
2639
|
+
if (index >= head && index < tail) {
|
|
2640
|
+
if (index === head) {
|
|
2641
|
+
const marker = omissionMarker(head, tail);
|
|
2642
|
+
// A sub-marker budget can retain no units. Keep its source-range
|
|
2643
|
+
// notice with the recovery footers rather than exceeding the body cap.
|
|
2644
|
+
if (Buffer.byteLength(marker, "utf-8") > capBytes) omissionNotice = marker;
|
|
2645
|
+
else {
|
|
2646
|
+
modelParts.push(marker);
|
|
2647
|
+
displayParts.push(marker);
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2605
2650
|
elidedSpans++;
|
|
2606
|
-
elidedLines +=
|
|
2651
|
+
elidedLines += unit.kind === "line" ? 1 : unit.endLine - unit.startLine + 1;
|
|
2652
|
+
} else {
|
|
2653
|
+
const part = rendered[index];
|
|
2654
|
+
modelParts.push(part.model);
|
|
2655
|
+
displayParts.push(part.display);
|
|
2656
|
+
if (unit.kind !== "line") elidedSpans++;
|
|
2657
|
+
elidedLines += part.implicitElidedLines;
|
|
2607
2658
|
}
|
|
2608
2659
|
}
|
|
2609
|
-
if (capDropped) {
|
|
2610
|
-
modelParts.push("…");
|
|
2611
|
-
displayParts.push("…");
|
|
2612
|
-
}
|
|
2613
2660
|
return {
|
|
2614
2661
|
text: modelParts.join("\n"),
|
|
2615
2662
|
displayText: displayParts.join("\n"),
|
|
2616
2663
|
elidedSpans,
|
|
2617
2664
|
elidedLines,
|
|
2618
2665
|
capped: capDropped,
|
|
2666
|
+
omissionNotice,
|
|
2619
2667
|
};
|
|
2620
2668
|
}
|
|
2621
2669
|
|
|
@@ -2911,27 +2959,25 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
2911
2959
|
(this.session.settings.get("read.summarize.prose") || !PROSE_SUMMARY_EXTENSIONS.has(ext))
|
|
2912
2960
|
) {
|
|
2913
2961
|
const summary = await this.#trySummarize(absolutePath, fileSize, signal);
|
|
2914
|
-
if (params.truncation !== undefined && params.truncation !== "head" && summary?.parsed) {
|
|
2915
|
-
throw new ToolError(
|
|
2916
|
-
"Explicit truncation for structural summaries is not yet supported; summary direction will be applied after summary units become directional.",
|
|
2917
|
-
);
|
|
2918
|
-
}
|
|
2919
2962
|
if (summary?.parsed && summary.elided) {
|
|
2920
|
-
const
|
|
2963
|
+
const direction = resolveEffectiveDirection(params.truncation, "local-summary", this.session.settings);
|
|
2964
|
+
const renderedSummary = this.#renderSummary(summary, direction);
|
|
2921
2965
|
const footer = formatSummaryElisionFooter(
|
|
2922
2966
|
localReadPath,
|
|
2923
2967
|
renderedSummary.elidedSpans,
|
|
2924
2968
|
renderedSummary.elidedLines,
|
|
2925
2969
|
);
|
|
2926
|
-
const summaryTotalLines =
|
|
2970
|
+
const summaryTotalLines = summary.totalLines;
|
|
2927
2971
|
const modelText = [
|
|
2928
2972
|
renderedSummary.text,
|
|
2973
|
+
renderedSummary.omissionNotice,
|
|
2929
2974
|
footer,
|
|
2930
2975
|
renderedSummary.capped
|
|
2931
2976
|
? formatSummaryCapFooter(
|
|
2932
2977
|
localReadPath,
|
|
2933
2978
|
this.session.settings.get("read.summaryMaxBytes") * 1024,
|
|
2934
2979
|
summaryTotalLines,
|
|
2980
|
+
direction,
|
|
2935
2981
|
)
|
|
2936
2982
|
: "",
|
|
2937
2983
|
]
|
|
@@ -3,6 +3,7 @@ import { prompt, untilAborted } from "@gajae-code/utils";
|
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
4
|
import {
|
|
5
5
|
describeDisabledSkillScopes,
|
|
6
|
+
describeNoSkillMatch,
|
|
6
7
|
discoverRuntimeSkills,
|
|
7
8
|
type RuntimeSkillDiscoveryCandidate,
|
|
8
9
|
} from "../extensibility/runtime-skill-discovery";
|
|
@@ -14,7 +15,9 @@ const skillDiscoverySchema = z
|
|
|
14
15
|
query: z
|
|
15
16
|
.string()
|
|
16
17
|
.optional()
|
|
17
|
-
.describe(
|
|
18
|
+
.describe(
|
|
19
|
+
"words to match against skill name, description, source, or use conditions; every term must appear (conjunctive substring), or one term must equal the exact skill name",
|
|
20
|
+
),
|
|
18
21
|
source: z.enum(["all", "project", "user"]).default("all").optional().describe("skill source scope to search"),
|
|
19
22
|
limit: z.number().min(1).max(50).default(20).optional().describe("maximum results"),
|
|
20
23
|
})
|
|
@@ -26,10 +29,12 @@ export interface SkillDiscoveryToolDetails {
|
|
|
26
29
|
candidates: RuntimeSkillDiscoveryCandidate[];
|
|
27
30
|
count: number;
|
|
28
31
|
/**
|
|
29
|
-
* Present only when zero candidates were returned
|
|
30
|
-
* (`skills.enabled` / `skills.trustProjectSkills` /
|
|
31
|
-
* prevented some or all of the requested scope from
|
|
32
|
-
*
|
|
32
|
+
* Present only when zero candidates were returned. Either discovery config
|
|
33
|
+
* gates (`skills.enabled` / `skills.trustProjectSkills` /
|
|
34
|
+
* `skills.trustUserSkills`) prevented some or all of the requested scope from
|
|
35
|
+
* being searched, a non-empty query filtered out every scanned skill, or
|
|
36
|
+
* diagnostics were produced for observed skills that were skipped or filtered.
|
|
37
|
+
* Without this, these cases are indistinguishable from "no skills exist".
|
|
33
38
|
*/
|
|
34
39
|
notice?: string;
|
|
35
40
|
/**
|
|
@@ -44,7 +49,7 @@ export interface SkillDiscoveryToolDetails {
|
|
|
44
49
|
export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema, SkillDiscoveryToolDetails> {
|
|
45
50
|
readonly name = "skill_discovery";
|
|
46
51
|
readonly label = "SkillDiscovery";
|
|
47
|
-
readonly summary = "Discover project and user runtime skills by thin metadata";
|
|
52
|
+
readonly summary = "Discover bundled GJC workflow, project, and user runtime skills by thin metadata";
|
|
48
53
|
readonly loadMode = "essential";
|
|
49
54
|
readonly description: string;
|
|
50
55
|
readonly parameters = skillDiscoverySchema;
|
|
@@ -95,7 +100,12 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
|
|
|
95
100
|
};
|
|
96
101
|
if (result.diagnostics.messages.length > 0) details.diagnostics = result.diagnostics.messages;
|
|
97
102
|
if (result.candidates.length === 0) {
|
|
98
|
-
const notice =
|
|
103
|
+
const notice =
|
|
104
|
+
describeDisabledSkillScopes(source, this.#getRuntimeSkillPolicy()) ??
|
|
105
|
+
describeNoSkillMatch(input.query, result.scanned) ??
|
|
106
|
+
(result.scanned > 0 || result.diagnostics.messages.length > 0
|
|
107
|
+
? `No skill candidates were returned (${result.scanned} skill${result.scanned === 1 ? "" : "s"} scanned); see diagnostics for filtered or skipped skills.`
|
|
108
|
+
: undefined);
|
|
99
109
|
if (notice) details.notice = notice;
|
|
100
110
|
}
|
|
101
111
|
return {
|
|
@@ -27,7 +27,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
27
27
|
"read": {
|
|
28
28
|
"name": "read",
|
|
29
29
|
"label": "Read",
|
|
30
|
-
"description": "Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.\n\n<instruction>\n- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).\n- You SHOULD parallelize independent reads when exploring related files.\n- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.\n</instruction>\n\n## Parameters\n\n- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).\n- `truncation` — optional `head` | `last` | `both`; selects which end of an over-budget result to retain. Configured default: last (factory default: `last`);
|
|
30
|
+
"description": "Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.\n\n<instruction>\n- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).\n- You SHOULD parallelize independent reads when exploring related files.\n- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.\n</instruction>\n\n## Parameters\n\n- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).\n- `truncation` — optional `head` | `last` | `both`; selects which end of an over-budget result to retain. Configured default: last (factory default: `last`); structural summaries and routes such as URLs, directories and converted documents default to `head`. A line-range selector still bounds the selection — this only picks which end of that selection survives the byte/line cap. SQLite row queries page via their own `limit`/`offset` and ignore it.\n## Selectors\nAppend `:<sel>` to `path`. The bare path falls back to the default mode.\n\n- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); a plain text file → a bounded receipt of about undefined lines or undefined KiB, whichever is smaller; the configured truncation direction is last (factory default: last). Line+hash anchors keep their real file line numbers and a footer names the omitted range. Archive members use the larger 3000-line / 50 KiB budget. Converted documents, notebooks, URLs and directory listings still start from the beginning.\n- `:50` / `:50-` — read from line 50 onward.\n- `:50-200` — lines 50–200 inclusive.\n- `:50+150` — 150 lines starting at line 50.\n- `:20+1` — exactly one line.\n- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged).\n- `:raw` — verbatim text; no anchors, no summary, no line prefixes.\n- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.\n- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.\n\n# Files\n\n- Reading a directory path returns a depth-limited dirent listing.\n- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:\n\n `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`\n\n If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.\n- Structural summaries honor explicit `truncation`: `head` retains the beginning, `last` the end, and `both` both ends within the summary byte budget. Complete summary units and their original source anchors stay intact and in source order; budget omissions are marked where they occur. This does not switch to raw source. Summaries that fit are unchanged by direction.\n- Directional windows identify the retained first/last lines and the omitted range; use the `re-read <path>:1-<total>` or `:raw` hint in the footer to recover the full content.\n\n# Documents & Notebooks\n\nExtracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.\n\n# Images\n\nReading an image path returns the image itself for visual inspection by a vision-capable model.\n\n# Archives\n\nSupports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.\n\n# SQLite\n\nFor `.sqlite`, `.sqlite3`, `.db`, `.db3`:\n- `file.db` — list tables with row counts\n- `file.db:table` — schema + sample rows\n- `file.db:table:key` — single row by primary key\n- `file.db:table?limit=50&offset=100` — paginated rows\n- `file.db:table?where=status='active'&order=created:desc` — filtered rows\n- `file.db?q=SELECT …` — read-only SELECT query\n\n# URLs\n\n- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.\n- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.\n- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.\n\n# Internal URIs\n\n`agent://<id>`, `artifact://<id>`, `rule://<name>`, and `local://<name>.md` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.\n\n<critical>\n- Always include `path`; never call `read` with `{}`.\n- For line ranges, append the selector to `path`.\n- Re-issue the selector named by a summary footer before relying on elided content.\n</critical>",
|
|
31
31
|
"parameters": {
|
|
32
32
|
"type": "object",
|
|
33
33
|
"properties": {
|
|
@@ -1109,7 +1109,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
1109
1109
|
"browser": {
|
|
1110
1110
|
"name": "browser",
|
|
1111
1111
|
"label": "Browser",
|
|
1112
|
-
"description": "Drives a real Chromium tab with full puppeteer access via JS execution.\n\n<instruction>\n- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL. Use this tool only when you need JS execution, authentication, or interactive actions.\n- Four actions:\n - `open` — acquire (or reuse) a named tab. `name` defaults to `\"main\"`. Optional `url`, `viewport`, and `dialogs: \"accept\" | \"dismiss\"` (auto-handles `alert`/`confirm`/`beforeunload`). The `app` field selects the browser kind (spawned binary, saved Chrome profile, or existing CDP endpoint); omitted means headless Chromium with stealth patches.\n - `close` — release a tab by `name`, or every tab with `all: true`. `kill: true` also terminates a spawned-app process tree.\n - `act` — run a list of structured `actions` against an existing tab without writing JS (preferred for routine navigation/interaction). Each step is `{ verb, … }`; verbs: `navigate {url, wait_until?}`, `click {id|selector}`, `type {id|selector, text}`, `fill {selector, value}`, `select {selector, values}`, `press {key, selector?}`, `scroll {dx?, dy?}`, `back`, `wait {selector?|ms?}`, `observe {viewport_only?, include_all?}`, `extract {format?}`, `screenshot`. Address elements by the numeric `id` from a prior `observe` (preferred) or a selector. Steps run in order; the tool returns per-step results.\n - `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The return value is JSON-stringified into the tool result; `display(value)` calls accumulate text/images. Use `run` only when an `act` verb does not cover what you need.\n- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.\n- Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: \"chrome\"` profile mode instead. In profile mode, `path` defaults to installed Chrome/Chromium and `profile_directory` defaults to `\"Default\"`, but `user_data_dir` must name a separate non-default Chrome data directory: Chrome 136+ disables remote debugging for its default data directory. Only Chrome/Chromium executables are admitted; Edge, Brave, Vivaldi, Opera, unknown browser brands, and default Chrome data roots are rejected. Use `app.cdp_url` to attach to an already-authorized browser. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.\n- Inside `run`, `tab` exposes high-level helpers (`goto`, `observe`, `id`, `click`, `type`, `fill`, `press`, `waitFor`, `screenshot`, `extract`, …); reach for `page` (raw puppeteer Page) when they don't cover it.\n- Selectors
|
|
1112
|
+
"description": "Drives a real Chromium tab with full puppeteer access via JS execution.\n\n<instruction>\n- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL. Use this tool only when you need JS execution, authentication, or interactive actions.\n- Four actions:\n - `open` — acquire (or reuse) a named tab. `name` defaults to `\"main\"`. Optional `url`, `viewport`, and `dialogs: \"accept\" | \"dismiss\"` (auto-handles `alert`/`confirm`/`beforeunload`). The `app` field selects the browser kind (spawned binary, saved Chrome profile, or existing CDP endpoint); omitted means headless Chromium with stealth patches.\n - `close` — release a tab by `name`, or every tab with `all: true`. `kill: true` also terminates a spawned-app process tree.\n - `act` — run a list of structured `actions` against an existing tab without writing JS (preferred for routine navigation/interaction). Each step is `{ verb, … }`; verbs: `navigate {url, wait_until?}`, `click {id|selector}`, `type {id|selector, text}`, `fill {selector, value}`, `select {selector, values}`, `press {key, selector?}`, `scroll {dx?, dy?}`, `back`, `wait {selector?|ms?}`, `observe {viewport_only?, include_all?}`, `extract {format?}`, `screenshot`. Address elements by the numeric `id` from a prior `observe` (preferred) or a selector. Steps run in order; the tool returns per-step results.\n - `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The return value is JSON-stringified into the tool result; `display(value)` calls accumulate text/images. Use `run` only when an `act` verb does not cover what you need.\n- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.\n- Browser kinds: no `app` launches headless Chromium; `app.path` reuses CDP or kills stale same-path processes before spawning — NEVER use it for a daily Chrome profile; use explicit `app.browser: \"chrome\"` profile mode instead. In profile mode, `path` defaults to installed Chrome/Chromium and `profile_directory` defaults to `\"Default\"`, but `user_data_dir` must name a separate non-default Chrome data directory: Chrome 136+ disables remote debugging for its default data directory. Only Chrome/Chromium executables are admitted; Edge, Brave, Vivaldi, Opera, unknown browser brands, and default Chrome data roots are rejected. Use `app.cdp_url` to attach to an already-authorized browser. Saved-profile/CDP automation has access to that profile's cookies and authenticated accounts. Profile mode refuses a matching non-CDP Chrome instead of killing/relaunching it, and `kill: true` can terminate only a Chrome process GJC launched; `app.cdp_url` is externally owned and disconnect-only. CDP must stay on `127.0.0.1`: it grants full browser-account access.\n- Inside `run`, `tab` exposes high-level helpers (`goto`, `observe`, `id`, `click`, `type`, `fill`, `press`, `waitFor`, `screenshot`, `extract`, …); reach for `page` (raw puppeteer Page) when they don't cover it.\n- Selectors must be non-empty strings: CSS or puppeteer query handlers (`aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`). In `run`, click numeric observation ids with `(await tab.id(58)).click()`, not `tab.click(58)`. Omit optional selectors for unfocused `tab.press()` or page-level `tab.screenshot()`; null and blank selectors are invalid.\n- Runtime diagnostics are opt-in: pass `diagnostics: true` to `open` to subscribe the tab to page `Runtime.exceptionThrown` and `console.error` events. The next successful `act`/`run` response then includes at most 20 `runtimeDiagnostics` entries plus `runtimeDiagnosticsDropped`, then drains them. Entries contain only kind, time, origin-only URL, line/column, and a built-in error class from a fixed allowlist — never path segments, query strings, messages, console arguments, values, or stacks. Output is byte-bounded and marks truncation explicitly.\n- Full reference — helpers, browser kinds, CDP/security details, and more examples — read `gjc://tools/browser.md`.\n</instruction>\n\n<critical>\n- You MUST call `open` before `run` or `act`. Neither implicitly creates a tab.\n- You MUST observe before taking a screenshot to understand page state; screenshot only when visual appearance matters.\n- After a `tab.goto()` or any navigation, prior element ids from `tab.observe()` are invalidated. Re-observe before referencing them.\n- `code` runs with full Node access. Treat it as your code, not sandboxed code.\n</critical>\n\n<examples>\n# Open a tab and read structured page data\n`{\"action\":\"open\",\"name\":\"docs\",\"url\":\"https://example.com\"}`\n`{\"action\":\"act\",\"name\":\"docs\",\"actions\":[{\"verb\":\"observe\"}]}`\n\n# Click an observed element, then fill and submit a form\n`{\"action\":\"act\",\"name\":\"docs\",\"actions\":[{\"verb\":\"click\",\"id\":12},{\"verb\":\"fill\",\"selector\":\"input[name=email]\",\"value\":\"me@example.com\"},{\"verb\":\"click\",\"selector\":\"text/Continue\"}]}`\n\n# Use `run` only when `act` has no suitable verb\n`{\"action\":\"run\",\"name\":\"docs\",\"code\":\"const count = await page.locator('canvas').count(); return { count };\"}`\n</examples>\n\n<output>\n- Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.\n</output>",
|
|
1113
1113
|
"parameters": {
|
|
1114
1114
|
"type": "object",
|
|
1115
1115
|
"properties": {
|
|
@@ -2068,7 +2068,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2068
2068
|
"task": {
|
|
2069
2069
|
"name": "task",
|
|
2070
2070
|
"label": "Task",
|
|
2071
|
-
"description": "Launches subagents to parallelize workflows.\n\n- Results are delivered automatically when complete.\n- The tool result lists the assigned task ids (e.g. `0-AuthLoader`) — those are the live agent ids.\n- Use `subagent` action `inspect` or `list` to snapshot manager state.\n- To wait or cancel, use the `subagent` tool; its await/cancel doctrine is authoritative.\n\nSubagents have no conversation history. Every fact, file path, and direction they need MUST be explicit in `context` or `assignment`.\n\n<parameters>\n- `agent`: agent type for all tasks\n- `tasks`: tasks to execute in parallel\n - `.id`: filesystem-safe, ≤48 chars, matching `[A-Za-z0-9][A-Za-z0-9_-]*`; prefer CamelCase\n - `.description`: UI label only — subagent never sees it\n - `.assignment`: complete self-contained instructions; one-liners and missing acceptance criteria are PROHIBITED\n- `context`: shared background prepended to every assignment; session-specific only\n- `.inheritContext` (optional): fork-context mode for seeding the subagent with sanitized parent conversation. Omit it or set `\"none\"` for no copied context. `\"receipt\"` copies a minimal receipt-sized snapshot, `\"last-turn\"` copies only the latest exchange, `\"bounded\"` copies the bounded default snapshot, and `\"full\"` copies a larger snapshot up to the configured/model token cap. Non-`none` modes work only when global `task.forkContext.enabled` is true and the target agent declares `forkContext: allowed`; otherwise the call is rejected. Bundled agents that support it: `executor`, `architect`. Use inherited context only when the subagent's value depends on parent context; cloned tokens are billed to the child as fresh input and surfaced in task receipts as fork-context cloned-token accounting.\n\n- `schema`: JTD schema for expected structured output (do not put format rules in assignments)\n- `spawnPlan` (optional): required before any batch with more than 4 tasks; include whyParallel, whyNotLocal, independence, expectedReceiptShape, and maxInlineTokens.\n</parameters>\n\n<rules>\n- HARD runtime gate: calls with more than 4 tasks are rejected before any child launches unless `spawnPlan` is complete.\n- NEVER assign tasks to run project-wide build/test/lint. Caller verifies after the batch.\n- **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates and formatters. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.\n- Each task: ≤3–5 explicit files. No globs, no \"update all\", no package-wide scope. Fan out to a cluster instead.\n- Pass large payloads via `local://<path>` URIs, not inline.\n- Put shared constraints in `context` once; do not duplicate across assignments.\n- Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown.\n</rules>\n\n<parallelization>\nTest: can task B run correctly without seeing A's output? If no, sequence A → B.\nSequential when one task produces a contract (types, API, schema, core module) the other consumes.\nParallel when tasks touch disjoint files or are independent refactors/tests.\n</parallelization>\n\n<context-fmt>\n# Goal ← one sentence: what the batch accomplishes\n# Constraints ← MUST/NEVER rules and session decisions\n# Contract ← exact types/signatures if tasks share an interface\n</context-fmt>\n\n<assignment-fmt>\n# Target ← exact files and symbols; explicit non-goals\n# Change ← step-by-step add/remove/rename; APIs and patterns\n# Acceptance ← observable result; no project-wide commands\n</assignment-fmt>\n\n<agents>\n# executor\nAutonomous implementation agent for bounded code changes, fixes, and verification-ready edits\n\n# architect\nRead-only architecture and code-review agent with severity-rated findings and status verdicts\n\n# planner\nRead-only planning agent for sequencing, acceptance criteria, risks, and handoff shape\n\n# critic\nRead-only plan critic that approves only actionable, verifiable execution plans\n</agents>",
|
|
2071
|
+
"description": "Launches subagents to parallelize workflows.\n\n- Results are delivered automatically when complete.\n- The tool result lists the assigned task ids (e.g. `0-AuthLoader`) — those are the live agent ids.\n- Use `subagent` action `inspect` or `list` to snapshot manager state.\n- To wait or cancel, use the `subagent` tool; its await/cancel doctrine is authoritative.\n\nSubagents have no conversation history. Every fact, file path, and direction they need MUST be explicit in `context` or `assignment`.\n\n<parameters>\n- `agent`: agent type for all tasks\n- `tasks`: tasks to execute in parallel\n - `.id`: filesystem-safe, ≤48 chars, matching `[A-Za-z0-9][A-Za-z0-9_-]*`; prefer CamelCase\n - `.description`: UI label only — subagent never sees it\n - `.assignment`: complete self-contained instructions; one-liners and missing acceptance criteria are PROHIBITED\n- `context`: shared background prepended to every assignment; session-specific only\n- `.inheritContext` (optional): fork-context mode for seeding the subagent with sanitized parent conversation. Omit it or set `\"none\"` for no copied context. `\"receipt\"` copies a minimal receipt-sized snapshot, `\"last-turn\"` copies only the latest exchange, `\"bounded\"` copies the bounded default snapshot, and `\"full\"` copies a larger snapshot up to the configured/model token cap. Non-`none` modes work only when global `task.forkContext.enabled` is true and the target agent declares `forkContext: allowed`; otherwise the call is rejected. Bundled agents that support it: `executor`, `architect`. Use inherited context only when the subagent's value depends on parent context; cloned tokens are billed to the child as fresh input and surfaced in task receipts as fork-context cloned-token accounting.\n\n- `schema`: JTD schema for expected structured output (do not put format rules in assignments)\n- `spawnPlan` (optional): required before any batch with more than 4 tasks; include whyParallel, whyNotLocal, independence, expectedReceiptShape, and maxInlineTokens.\n\n- `repositoryBinding`: optional authoritative repository/worktree identity. It must match the active session and cannot authorize a foreign repository from this session. For another approved repository, start a fresh session rooted there (for example, `gjc --cwd <approved-worktree>`); do not use `/move` because materialized tasks retain their original binding.\n</parameters>\n\n<rules>\n- HARD runtime gate: calls with more than 4 tasks are rejected before any child launches unless `spawnPlan` is complete.\n- NEVER assign tasks to run project-wide build/test/lint. Caller verifies after the batch.\n- **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates and formatters. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.\n- Each task: ≤3–5 explicit files. No globs, no \"update all\", no package-wide scope. Fan out to a cluster instead.\n- Pass large payloads via `local://<path>` URIs, not inline.\n- Put shared constraints in `context` once; do not duplicate across assignments.\n- Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown.\n</rules>\n\n<parallelization>\nTest: can task B run correctly without seeing A's output? If no, sequence A → B.\nSequential when one task produces a contract (types, API, schema, core module) the other consumes.\nParallel when tasks touch disjoint files or are independent refactors/tests.\n</parallelization>\n\n<context-fmt>\n# Goal ← one sentence: what the batch accomplishes\n# Constraints ← MUST/NEVER rules and session decisions\n# Contract ← exact types/signatures if tasks share an interface\n</context-fmt>\n\n<assignment-fmt>\n# Target ← exact files and symbols; explicit non-goals\n# Change ← step-by-step add/remove/rename; APIs and patterns\n# Acceptance ← observable result; no project-wide commands\n</assignment-fmt>\n\n<agents>\n# executor\nAutonomous implementation agent for bounded code changes, fixes, and verification-ready edits\n\n# architect\nRead-only architecture and code-review agent with severity-rated findings and status verdicts\n\n# planner\nRead-only planning agent for sequencing, acceptance criteria, risks, and handoff shape\n\n# critic\nRead-only plan critic that approves only actionable, verifiable execution plans\n</agents>",
|
|
2072
2072
|
"parameters": {
|
|
2073
2073
|
"type": "object",
|
|
2074
2074
|
"properties": {
|
|
@@ -2123,7 +2123,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2123
2123
|
]
|
|
2124
2124
|
},
|
|
2125
2125
|
"repositoryBinding": {
|
|
2126
|
-
"description": "authoritative repository identity; omitted items are stamped from session cwd before discovery/spawn and still fail closed on sibling drift",
|
|
2126
|
+
"description": "authoritative repository identity; omitted items are stamped from session cwd before discovery/spawn and still fail closed on sibling drift. A declared binding must match the active session; use a new session rooted in the approved worktree (for example, gjc --cwd <approved-worktree>) for another repository.",
|
|
2127
2127
|
"type": "object",
|
|
2128
2128
|
"properties": {
|
|
2129
2129
|
"schema": {
|
|
@@ -2753,12 +2753,12 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2753
2753
|
"skill_discovery": {
|
|
2754
2754
|
"name": "skill_discovery",
|
|
2755
2755
|
"label": "SkillDiscovery",
|
|
2756
|
-
"description": "Discover project and user runtime skills without loading full skill content.\n\n<instruction>\n-
|
|
2756
|
+
"description": "Discover bundled GJC workflow, project, and user runtime skills without loading full skill content.\n\n<instruction>\n- Includes the four bundled GJC workflow skills (`autoresearch`, `deep-interview`, `ralplan`, `ultragoal`) as `source: \"bundled\"` candidates from the generated bundled catalog; they are always available, cannot be replaced by filesystem copies, and are included for `source: \"all\"` but excluded by explicit `source: \"project\"`/`\"user\"` requests.\n- Returns thin metadata only: name, description, source scope, path, and use conditions when present. Bundled paths are stable catalog identifiers, not filesystem paths.\n- In trusted Git projects, `.gjc/skills` may be a symlink to a directory inside the same repository (for example `../.agents/skills`). Link and target identities remain checked through body loading; external targets and symlinked user/profile roots are refused. Do not replace a safe project link with copied files to make discovery work.\n- Claude Code (`.claude/skills`) and Codex (`.codex/skills`) layouts are explicit import sources into `.gjc`, never invokable candidates. They are not returned as candidates; instead, each convention skill found in a trusted scope is reported in `diagnostics` with the exact copy command that enables it (copy into `.gjc/skills`), so a skill placed in a documented convention location is discoverable in a normal session without being silently loaded.\n- Discovery is on by default in a normal session. `query` matching is conjunctive substring: every whitespace-separated term must appear in a skill's name, description, source, or use conditions, unless a term equals the exact skill name — one keyword that appears nowhere in the candidate's metadata drops every candidate, so query with exact names or few terms known to appear literally, not topic keyword lists. When zero candidates are returned, the result carries a `notice` if discovery config disabled a requested scope, a non-empty query filtered out scanned skills, or diagnostics explain observed skills that were skipped or filtered. Only an empty result without a `notice` means no skills were scanned and no diagnostics were produced; never report a zero-candidate result as proof no skill exists.\n- When skills were scanned but not advertised (protected-name collision with a bundled workflow skill, include/ignore/disable policy filters, invalid frontmatter, shadowing), the result carries a bounded `diagnostics` list explaining why.\n- To load a selected skill's full `SKILL.md`, invoke it through the existing `skill` tool with the exact `name` returned here.\n</instruction>\n\nInput:\n- `query` (optional): words to match against skill name, description, source, or use conditions. Every term must appear (conjunctive substring), or one term must equal the exact skill name.\n- `source` (optional): `all`, `project`, or `user`.\n- `limit` (optional): maximum results, 1-50.",
|
|
2757
2757
|
"parameters": {
|
|
2758
2758
|
"type": "object",
|
|
2759
2759
|
"properties": {
|
|
2760
2760
|
"query": {
|
|
2761
|
-
"description": "words to match against skill name, description, source, or use conditions",
|
|
2761
|
+
"description": "words to match against skill name, description, source, or use conditions; every term must appear (conjunctive substring), or one term must equal the exact skill name",
|
|
2762
2762
|
"type": "string"
|
|
2763
2763
|
},
|
|
2764
2764
|
"source": {
|
|
@@ -2784,7 +2784,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2784
2784
|
"strict": true,
|
|
2785
2785
|
"deferrable": false,
|
|
2786
2786
|
"loadMode": "essential",
|
|
2787
|
-
"summary": "Discover project and user runtime skills by thin metadata"
|
|
2787
|
+
"summary": "Discover bundled GJC workflow, project, and user runtime skills by thin metadata"
|
|
2788
2788
|
},
|
|
2789
2789
|
"telegram_send": {
|
|
2790
2790
|
"name": "telegram_send",
|
|
@@ -2904,7 +2904,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2904
2904
|
"move_session": {
|
|
2905
2905
|
"name": "move_session",
|
|
2906
2906
|
"label": "Move Session",
|
|
2907
|
-
"description": "Rescope the session to a narrower working directory.\n\nUse this only when the session's working directory is a broad launcher root (for example a\nmulti-repo workspace like `~/Projects`) and the task has clearly converged on one subdirectory\nor repository: after this call, every later turn resolves relative paths and the bash default\ncwd from the new directory, and project-scoped plugins/capabilities reload for it.\n\n- `path` must be an existing directory; relative paths resolve against the current session cwd.\n The canonical target must be strictly inside the current session directory — moves to a\n parent, a sibling project, or an unrelated absolute path are refused.\n- A session can be moved this way at most once, and never while another move is running; a\n rejected call does not consume the move. Use it once the target repo is identified — not\n speculatively — because the session file and caches move with the session.\n- This tool is unavailable in subagent sessions and restricted profiles; ask the top-level\n session to rescope instead.",
|
|
2907
|
+
"description": "Rescope the session to a narrower working directory.\n\nUse this only when the session's working directory is a broad launcher root (for example a\nmulti-repo workspace like `~/Projects`) and the task has clearly converged on one subdirectory\nor repository: after this call, every later turn resolves relative paths and the bash default\ncwd from the new directory, and project-scoped plugins/capabilities reload for it.\n\n- `path` must be an existing directory; relative paths resolve against the current session cwd.\n The canonical target must be strictly inside the current session directory — moves to a\n parent, a sibling project, or an unrelated absolute path are refused.\n- A session can be moved this way at most once, and never while another move is running; a\n rejected call does not consume the move. Use it once the target repo is identified — not\n speculatively — because the session file and caches move with the session.\n- This tool is unavailable in subagent sessions and restricted profiles; ask the top-level\n session to rescope instead.\n- For an explicitly requested sibling or unrelated repository, start a new session with\n `gjc --cwd <approved-worktree>` and delegate from there. A task's `repositoryBinding`\n verifies the session's repository; it cannot authorize relocation. Already-created task\n tools retain their original repository binding even after a user-driven cwd move.",
|
|
2908
2908
|
"parameters": {
|
|
2909
2909
|
"type": "object",
|
|
2910
2910
|
"properties": {
|
|
@@ -11,28 +11,16 @@ const ULTRAGOAL_ASK_GUARD = Symbol.for("gajae-code.ultragoalAskGuard");
|
|
|
11
11
|
type GuardedTool = AgentTool & { [ULTRAGOAL_ASK_GUARD]?: true };
|
|
12
12
|
|
|
13
13
|
export interface UltragoalAskGuardContext {
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* GJC session id of the session asking the question. This is the guard's only
|
|
16
|
+
* identity input: durable Ultragoal state and the nudge budget are bound to it
|
|
17
|
+
* exclusively. Active-skill metadata is deliberately NOT part of this context —
|
|
18
|
+
* a snapshot restored from another session carries that session's id, and
|
|
19
|
+
* treating it as the caller's identity leaked a foreign run into the guard.
|
|
20
|
+
*/
|
|
15
21
|
sessionId?: string | null;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
const UPSTREAM_PLANNING_ASK_SKILLS = new Set(["deep-interview", "ralplan"]);
|
|
19
|
-
|
|
20
|
-
function normalizedActiveSkill(context?: UltragoalAskGuardContext): string | undefined {
|
|
21
|
-
const skill = context?.activeSkillState?.skill?.trim();
|
|
22
|
-
return skill || undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function sessionScopedAskGuardId(
|
|
26
|
-
context: UltragoalAskGuardContext,
|
|
27
|
-
activeSkill: string | undefined,
|
|
28
|
-
): string | undefined {
|
|
29
|
-
if (activeSkill !== "ultragoal" && !UPSTREAM_PLANNING_ASK_SKILLS.has(activeSkill ?? "")) return undefined;
|
|
30
|
-
const activeSessionId = context.activeSkillState?.session_id?.trim();
|
|
31
|
-
if (activeSessionId) return activeSessionId;
|
|
32
|
-
const sessionId = context.sessionId?.trim();
|
|
33
|
-
return sessionId || undefined;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
24
|
export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiagnostic): string {
|
|
37
25
|
return [
|
|
38
26
|
diagnostic.message,
|
|
@@ -46,13 +34,13 @@ export async function assertUltragoalAskAllowed(
|
|
|
46
34
|
context: UltragoalAskGuardContext = {},
|
|
47
35
|
agentDir?: string,
|
|
48
36
|
): Promise<void> {
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
// Durable Ultragoal state is session-scoped, so only the caller's own run may
|
|
38
|
+
// block `ask`. An anonymous caller has no run attributable to it, so the guard
|
|
39
|
+
// fails open instead of falling back to GJC_SESSION_ID or an auto-detected
|
|
40
|
+
// session: either can name another (possibly dead) session's abandoned run and
|
|
41
|
+
// would make `ask` unusable for every session in the repository.
|
|
42
|
+
const sessionId = context.sessionId?.trim();
|
|
43
|
+
if (!sessionId) return;
|
|
56
44
|
const diagnostic = await isUltragoalAskBlocked(cwd, { sessionId });
|
|
57
45
|
if (!diagnostic.active) return;
|
|
58
46
|
const nudge = await consumeUltragoalAskNudge(cwd, sessionId, agentDir);
|
|
@@ -76,6 +64,8 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(
|
|
|
76
64
|
return async (...args: unknown[]): Promise<unknown> => {
|
|
77
65
|
// The wrapper runs BEFORE AskTool.execute(): resolve the nudge
|
|
78
66
|
// budget against the SESSION profile, never the process-global one.
|
|
67
|
+
// The caller session id is the only binding, so a foreign session's
|
|
68
|
+
// durable run can never gate this ask.
|
|
79
69
|
await assertUltragoalAskAllowed(getCwd(), getContext(), getSessionAgentDir());
|
|
80
70
|
return Reflect.apply(target.execute, target, args);
|
|
81
71
|
};
|
package/src/utils/changelog.ts
CHANGED
|
@@ -87,9 +87,15 @@ export async function parseChangelog(changelogPath: string): Promise<ChangelogEn
|
|
|
87
87
|
* dev runs, and `GJC_PACKAGE_DIR` / `PI_PACKAGE_DIR` overrides (which scope to
|
|
88
88
|
* optional package assets like docs/examples and do not influence the
|
|
89
89
|
* binary-identity changelog).
|
|
90
|
+
*
|
|
91
|
+
* Parsed once per process and cached: the embedded text is immutable, and the
|
|
92
|
+
* parse runs on the interactive startup path plus every /changelog invocation.
|
|
90
93
|
*/
|
|
94
|
+
let displayChangelogEntriesCache: ChangelogEntry[] | undefined;
|
|
95
|
+
|
|
91
96
|
export function getDisplayChangelogEntries(): ChangelogEntry[] {
|
|
92
|
-
|
|
97
|
+
displayChangelogEntriesCache ??= parseChangelogContent(CHANGELOG_TEXT);
|
|
98
|
+
return displayChangelogEntriesCache;
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
export function getInstalledVersionChangelogEntry(
|
package/src/utils/git.ts
CHANGED
|
@@ -1266,7 +1266,9 @@ export async function clone(url: string, targetDir: string, options: CloneOption
|
|
|
1266
1266
|
const args = ["clone", "--depth", "1"];
|
|
1267
1267
|
if (options.ref) args.push("--branch", options.ref, "--single-branch");
|
|
1268
1268
|
else args.push("--single-branch");
|
|
1269
|
-
|
|
1269
|
+
// `--` ends option parsing, so a URL that begins with `-` can never be read as
|
|
1270
|
+
// a git option (`--upload-pack=...` and friends).
|
|
1271
|
+
args.push("--", url, absoluteTarget);
|
|
1270
1272
|
|
|
1271
1273
|
try {
|
|
1272
1274
|
await runEffect(path.dirname(absoluteTarget), args, { signal: options.signal });
|
package/src/utils/markit.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { untilAborted } from "@gajae-code/utils";
|
|
2
2
|
import type { Markit, StreamInfo } from "markit-ai";
|
|
3
3
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
4
|
+
import { ensureMupdfWasmResolution } from "./mupdf-wasm";
|
|
4
5
|
|
|
5
6
|
export interface MarkitConversionResult {
|
|
6
7
|
content: string;
|
|
@@ -18,6 +19,11 @@ let markit: () => Markit | Promise<Markit> = async () => {
|
|
|
18
19
|
return promise;
|
|
19
20
|
};
|
|
20
21
|
|
|
22
|
+
// #5433: mupdf's wasm asset is not reachable at the Emscripten loader's
|
|
23
|
+
// default bunfs path in compiled binaries. Seed the embedded-asset hook
|
|
24
|
+
// before any markit conversion (and therefore before any mupdf import).
|
|
25
|
+
ensureMupdfWasmResolution();
|
|
26
|
+
|
|
21
27
|
function normalizeExtension(extension: string): string {
|
|
22
28
|
const trimmed = extension.trim().toLowerCase();
|
|
23
29
|
if (!trimmed) return ".bin";
|
|
@@ -31,6 +37,22 @@ function normalizeError(error: unknown): string {
|
|
|
31
37
|
return "Conversion failed";
|
|
32
38
|
}
|
|
33
39
|
|
|
40
|
+
/**
|
|
41
|
+
* markit-ai's PDF converter swallows the real mupdf import failure behind a
|
|
42
|
+
* generic "Install it" message (#5433). Re-run the import here so release
|
|
43
|
+
* diagnostics carry the actual module/asset/initialization error.
|
|
44
|
+
*/
|
|
45
|
+
async function describeConversionError(error: unknown): Promise<string> {
|
|
46
|
+
const base = normalizeError(error);
|
|
47
|
+
if (!base.includes("PDF support requires 'mupdf'")) return base;
|
|
48
|
+
try {
|
|
49
|
+
await import("mupdf");
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
return `${base} (mupdf import failed: ${normalizeError(cause)})`;
|
|
52
|
+
}
|
|
53
|
+
return base;
|
|
54
|
+
}
|
|
55
|
+
|
|
34
56
|
async function runMarkitConversion<T>(task: (markit: Markit) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
35
57
|
try {
|
|
36
58
|
const instance = await markit();
|
|
@@ -62,7 +84,7 @@ export async function convertFileWithMarkit(filePath: string, signal?: AbortSign
|
|
|
62
84
|
if (error instanceof ToolAbortError) {
|
|
63
85
|
throw error;
|
|
64
86
|
}
|
|
65
|
-
return { content: "", ok: false, error:
|
|
87
|
+
return { content: "", ok: false, error: await describeConversionError(error) };
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
|
|
@@ -84,6 +106,6 @@ export async function convertBufferWithMarkit(
|
|
|
84
106
|
if (error instanceof ToolAbortError) {
|
|
85
107
|
throw error;
|
|
86
108
|
}
|
|
87
|
-
return { content: "", ok: false, error:
|
|
109
|
+
return { content: "", ok: false, error: await describeConversionError(error) };
|
|
88
110
|
}
|
|
89
111
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MuPDF wasm asset resolution inside compiled binaries (#5433).
|
|
3
|
+
*
|
|
4
|
+
* `mupdf`'s Emscripten loader resolves `mupdf-wasm.wasm` relative to
|
|
5
|
+
* `import.meta.url` (or `scriptDirectory`). Inside a `bun build --compile`
|
|
6
|
+
* bunfs that path points at the bunfs root where the asset does not exist,
|
|
7
|
+
* so every mupdf import aborted with
|
|
8
|
+
* `ENOENT: ... /$bunfs/root/mupdf-wasm.wasm`.
|
|
9
|
+
*
|
|
10
|
+
* The wasm is embedded via `with { type: "file" }`, which lands it at a
|
|
11
|
+
* hashed bunfs path. MuPDF's top-level factory call reads
|
|
12
|
+
* `globalThis["$libmupdf_wasm_Module"]`, so seeding that config with a
|
|
13
|
+
* `locateFile` hook that returns the embedded asset path makes the loader
|
|
14
|
+
* read the wasm from the bunfs directly — no disk sidecar needed.
|
|
15
|
+
*
|
|
16
|
+
* This must run before the first `import("mupdf")` anywhere in the process.
|
|
17
|
+
*/
|
|
18
|
+
import mupdfWasmPath from "../../../../node_modules/mupdf/dist/mupdf-wasm.wasm" with { type: "file" };
|
|
19
|
+
|
|
20
|
+
const MODULE_CONFIG_KEY = "$libmupdf_wasm_Module";
|
|
21
|
+
|
|
22
|
+
export function ensureMupdfWasmResolution(): void {
|
|
23
|
+
const globalScope = globalThis as typeof globalThis & Record<string, unknown>;
|
|
24
|
+
if (globalScope[MODULE_CONFIG_KEY] !== undefined) return;
|
|
25
|
+
globalScope[MODULE_CONFIG_KEY] = { locateFile: () => String(mupdfWasmPath) };
|
|
26
|
+
}
|