@gajae-code/coding-agent 0.18.7 → 0.18.9
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 +377 -1
- package/dist/types/async/endpoint-id.d.ts +16 -0
- package/dist/types/async/job-manager.d.ts +1 -1
- package/dist/types/async/support.d.ts +0 -14
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/capability/skill.d.ts +2 -0
- package/dist/types/capability/types.d.ts +2 -0
- package/dist/types/cli/public-command-errors.d.ts +3 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/mcp-serve.d.ts +4 -0
- package/dist/types/config/autorouting-tier-map.d.ts +348 -9
- package/dist/types/config/file-lock.d.ts +1 -0
- package/dist/types/config/model-bindings-applier.d.ts +2 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -3
- package/dist/types/config/model-registry.d.ts +4 -9
- package/dist/types/config/skill-settings-defaults.d.ts +2 -0
- package/dist/types/cursor-delete-path.d.ts +1 -0
- package/dist/types/cursor-mcp-name.d.ts +8 -0
- package/dist/types/customization/inventory.d.ts +2 -0
- package/dist/types/debug/report-bundle-parent.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
- package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
- package/dist/types/discovery/helpers.d.ts +6 -1
- package/dist/types/discovery/mcp-json.d.ts +1 -0
- package/dist/types/eval/py/kernel.d.ts +18 -0
- package/dist/types/export/html/css-color.d.ts +2 -0
- package/dist/types/export/html/html-export-destination.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/function-hooks-internal.d.ts +5 -0
- package/dist/types/extensibility/extensions/types.d.ts +7 -0
- package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
- package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
- package/dist/types/extensibility/skill-management.d.ts +4 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -3
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
- package/dist/types/gjc-runtime/sidecar-bootstrap-env.d.ts +6 -0
- package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
- package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +14 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +8 -5
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
- package/dist/types/internal-urls/skill-path.d.ts +1 -0
- package/dist/types/lsp/config.d.ts +8 -5
- package/dist/types/lsp/workspace-path.d.ts +1 -0
- package/dist/types/modes/acp/acp-agent.d.ts +7 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
- package/dist/types/modes/controllers/input-controller.d.ts +9 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
- package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
- package/dist/types/progress/collect-project-progress.d.ts +38 -0
- package/dist/types/progress/progress-contract.d.ts +154 -0
- package/dist/types/progress/progress-snapshot.d.ts +11 -0
- package/dist/types/progress/project-progress.d.ts +141 -0
- package/dist/types/progress/render-progress.d.ts +28 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -1
- package/dist/types/runtime-mcp/mcp-redirect.d.ts +9 -0
- package/dist/types/runtime-mcp/oauth-public-url.d.ts +3 -0
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
- package/dist/types/runtime-mcp/project-host-values.d.ts +27 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk/acp/adapter.d.ts +1 -1
- package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
- package/dist/types/sdk/broker/broker.d.ts +24 -2
- package/dist/types/sdk/broker/doctor-restart.d.ts +5 -0
- package/dist/types/sdk/broker/ensure.d.ts +14 -1
- package/dist/types/sdk/broker/session-index.d.ts +12 -6
- package/dist/types/sdk/bus/index.d.ts +12 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
- package/dist/types/sdk/client/client.d.ts +2 -0
- package/dist/types/sdk/host/query/handlers.d.ts +3 -0
- package/dist/types/sdk/index.d.ts +1 -0
- package/dist/types/sdk/router/session-router.d.ts +2 -0
- package/dist/types/sdk/session-list.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +8 -0
- package/dist/types/session/agent-session.d.ts +38 -9
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
- package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
- package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
- package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
- package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +31 -5
- package/dist/types/session/session-retirement.d.ts +31 -7
- package/dist/types/session/session-storage.d.ts +69 -2
- package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
- package/dist/types/setup/credential-auto-import.d.ts +1 -1
- package/dist/types/setup/credential-import.d.ts +6 -4
- package/dist/types/skill-state/active-state.d.ts +38 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +3 -0
- package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/isolation-dir.d.ts +26 -0
- package/dist/types/tools/ast-edit-write-path.d.ts +13 -0
- package/dist/types/tools/bash-skill-urls.d.ts +1 -1
- package/dist/types/tools/browser/cdp-url.d.ts +2 -0
- package/dist/types/tools/github-side-effect.d.ts +22 -0
- package/dist/types/tools/index.d.ts +3 -0
- package/dist/types/tools/monitor-mutation.d.ts +9 -0
- package/dist/types/tools/python.d.ts +9 -1
- package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
- package/dist/types/tools/restricted-cwd.d.ts +1 -0
- package/dist/types/tools/todo-write.d.ts +6 -0
- package/dist/types/utils/tools-checksum.d.ts +7 -0
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +21 -0
- package/scripts/verify-managed-empty-stop.ts +28 -0
- package/src/async/endpoint-id.ts +26 -0
- package/src/async/job-manager.ts +247 -132
- package/src/async/support.ts +0 -25
- package/src/capability/index.ts +15 -0
- package/src/capability/mcp.ts +2 -0
- package/src/capability/skill.ts +2 -0
- package/src/capability/types.ts +2 -0
- package/src/cli/auth-broker-cli.ts +1 -1
- package/src/cli/customize-doctor.ts +15 -2
- package/src/cli/doctor/repairs.ts +4 -1
- package/src/cli/doctor-supervisor.ts +3 -0
- package/src/cli/public-command-errors.ts +21 -1
- package/src/cli/public-command-registry.ts +28 -6
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/skills-cli.ts +5 -0
- package/src/commands/deep-interview.ts +47 -8
- package/src/commands/mcp-serve.ts +7 -0
- package/src/commands/sdk.ts +455 -397
- package/src/config/autorouting-tier-map.ts +117 -7
- package/src/config/file-lock.ts +316 -79
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-bindings-applier.ts +20 -0
- package/src/config/model-profile-activation.ts +240 -46
- package/src/config/model-registry.ts +47 -153
- package/src/config/skill-settings-defaults.ts +7 -0
- package/src/coordinator-mcp/policy.ts +3 -2
- package/src/coordinator-mcp/server.ts +2 -0
- package/src/cursor-delete-path.ts +10 -0
- package/src/cursor-mcp-name.ts +15 -0
- package/src/cursor.ts +13 -2
- package/src/customization/inventory.ts +4 -13
- package/src/dap/config.ts +2 -2
- package/src/debug/report-bundle-parent.ts +22 -0
- package/src/debug/report-bundle.ts +3 -0
- package/src/debug/system-info.ts +13 -1
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +10 -6
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
- package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
- package/src/defaults/gjc-defaults.test.ts +18 -1
- package/src/defaults/gjc-defaults.ts +3 -0
- package/src/defaults/gjc-skills.generated.ts +58 -36
- package/src/discovery/agents.ts +6 -1
- package/src/discovery/builtin.ts +13 -1
- package/src/discovery/helpers.ts +171 -29
- package/src/discovery/mcp-json.ts +26 -12
- package/src/discovery/ssh.ts +1 -1
- package/src/discovery/substitute-plugin-root.ts +3 -1
- package/src/edit/file-read-cache.ts +1 -1
- package/src/eval/py/executor.ts +796 -154
- package/src/eval/py/kernel.ts +79 -13
- package/src/eval/py/tool-bridge.ts +4 -0
- package/src/export/html/css-color.ts +9 -0
- package/src/export/html/html-export-destination.ts +16 -0
- package/src/export/html/index.ts +7 -10
- package/src/export/html/template.generated.ts +1 -1
- package/src/extensibility/extensions/function-hooks-internal.ts +27 -0
- package/src/extensibility/extensions/runner.ts +15 -1
- package/src/extensibility/extensions/types.ts +7 -0
- package/src/extensibility/gjc-plugins/mcp-policy.ts +2 -1
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +85 -8
- package/src/extensibility/plugins/manager.ts +3 -2
- package/src/extensibility/plugins/marketplace/cache.ts +15 -0
- package/src/extensibility/plugins/marketplace/manager.ts +11 -3
- package/src/extensibility/plugins/plugin-link-path.ts +12 -0
- package/src/extensibility/runtime-skill-discovery.ts +20 -2
- package/src/extensibility/skill-management.ts +9 -2
- package/src/extensibility/skills.ts +6 -1
- package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
- package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
- package/src/gjc-runtime/deep-interview-stage.ts +98 -21
- package/src/gjc-runtime/gc-runtime.ts +206 -9
- package/src/gjc-runtime/launch-worktree.ts +70 -9
- package/src/gjc-runtime/psmux-detect.ts +28 -4
- package/src/gjc-runtime/ralplan-runtime.ts +447 -52
- package/src/gjc-runtime/session-state-sidecar.ts +3 -2
- package/src/gjc-runtime/sidecar-bootstrap-env.ts +19 -0
- package/src/gjc-runtime/state-argv.ts +2 -0
- package/src/gjc-runtime/state-migrations.ts +91 -29
- package/src/gjc-runtime/state-runtime.ts +3716 -135
- package/src/gjc-runtime/state-writer.ts +105 -16
- package/src/gjc-runtime/tmux-common.ts +8 -5
- package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
- package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
- package/src/gjc-runtime/workflow-manifest.ts +54 -21
- package/src/harness-control-plane/operate.ts +12 -4
- package/src/harness-control-plane/owner.ts +5 -1
- package/src/hashline/parser.ts +48 -9
- package/src/hashline/recovery.ts +13 -4
- package/src/internal-urls/docs-index.generated.ts +20 -19
- package/src/internal-urls/skill-path.ts +19 -0
- package/src/internal-urls/skill-protocol.ts +2 -0
- package/src/lsp/config.ts +12 -63
- package/src/lsp/edits.ts +6 -0
- package/src/lsp/workspace-path.ts +51 -0
- package/src/main.ts +195 -31
- package/src/memories/index.ts +88 -7
- package/src/modes/acp/acp-agent.ts +88 -4
- package/src/modes/components/custom-editor.ts +14 -0
- package/src/modes/components/customization/customization-dashboard.ts +12 -0
- package/src/modes/components/extensions/extension-dashboard.ts +2 -2
- package/src/modes/components/extensions/state-manager.ts +15 -5
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +1 -1
- package/src/modes/components/model-selector.ts +1 -4
- package/src/modes/components/provider-onboarding-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +4 -0
- package/src/modes/controllers/event-controller.ts +11 -4
- package/src/modes/controllers/input-controller.ts +76 -31
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -16
- package/src/modes/controllers/selector-controller.ts +24 -15
- package/src/modes/controllers/todo-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +5 -1
- package/src/modes/runtime-init.ts +4 -0
- package/src/modes/utils/abort-message.ts +3 -1
- package/src/modes/utils/interrupt-ownership.ts +12 -0
- package/src/modes/utils/keybinding-matchers.ts +4 -0
- package/src/modes/utils/macos-option-forwarding.ts +80 -0
- package/src/progress/collect-project-progress.ts +153 -0
- package/src/progress/progress-contract.ts +172 -0
- package/src/progress/progress-snapshot.ts +140 -0
- package/src/progress/project-progress.ts +446 -0
- package/src/progress/render-progress.ts +209 -0
- package/src/prompts/system/master-mode.md +1 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/runtime-mcp/client.ts +4 -2
- package/src/runtime-mcp/config-writer.ts +10 -6
- package/src/runtime-mcp/config.ts +1 -0
- package/src/runtime-mcp/json-rpc.ts +2 -1
- package/src/runtime-mcp/manager.ts +29 -11
- package/src/runtime-mcp/mcp-redirect.ts +73 -0
- package/src/runtime-mcp/oauth-flow.ts +14 -2
- package/src/runtime-mcp/oauth-public-url.ts +14 -0
- package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
- package/src/runtime-mcp/pool.ts +6 -0
- package/src/runtime-mcp/project-host-values.ts +46 -0
- package/src/runtime-mcp/smithery-registry.ts +22 -3
- package/src/runtime-mcp/transports/http.ts +4 -3
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk/acp/adapter.ts +95 -8
- package/src/sdk/broker/broker-exit.ts +52 -23
- package/src/sdk/broker/broker.ts +81 -6
- package/src/sdk/broker/doctor-restart.ts +4 -0
- package/src/sdk/broker/ensure.ts +31 -2
- package/src/sdk/broker/lifecycle.ts +711 -66
- package/src/sdk/broker/session-index.ts +191 -81
- package/src/sdk/bus/chat-daemon-cli.ts +38 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +238 -43
- package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
- package/src/sdk/cli/session-cli.ts +17 -3
- package/src/sdk/client/client.ts +28 -8
- package/src/sdk/host/query/handlers.ts +9 -1
- package/src/sdk/host/session-runtime.test.ts +2705 -303
- package/src/sdk/host/session-runtime.ts +490 -143
- package/src/sdk/host/surface-policy.ts +1 -0
- package/src/sdk/index.ts +8 -0
- package/src/sdk/lifecycle-session.ts +3 -1
- package/src/sdk/prompt-deadline-manager.ts +15 -0
- package/src/sdk/protocol/operation-inventory.generated.json +146 -0
- package/src/sdk/protocol/operation-registry.ts +9 -2
- package/src/sdk/router/session-router.ts +82 -18
- package/src/sdk/session-list.ts +1 -0
- package/src/sdk/session.ts +69 -45
- package/src/session/agent-session.ts +679 -238
- package/src/session/artifacts.ts +176 -71
- package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
- package/src/session/internal/managed-session-scope.ts +3779 -462
- package/src/session/internal/managed-session-storage.ts +501 -78
- package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
- package/src/session/internal/task-artifact-owner-access.ts +19 -3
- package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
- package/src/session/session-manager.ts +951 -133
- package/src/session/session-retirement.ts +573 -14
- package/src/session/session-storage.ts +447 -15
- package/src/session/task-artifact-owner-codec.ts +71 -8
- package/src/session/task-artifact-owner-retirement.ts +21 -19
- package/src/setup/credential-auto-import.ts +12 -3
- package/src/setup/credential-import.ts +176 -5
- package/src/setup/paseo/announce.ts +6 -1
- package/src/skill-state/active-state.ts +145 -2
- package/src/skill-state/workflow-mutation-guard.ts +102 -10
- package/src/slash-commands/builtin-registry.ts +39 -9
- package/src/slash-commands/helpers/mcp.ts +8 -4
- package/src/slash-commands/helpers/progress-report.ts +23 -0
- package/src/stt/setup.ts +3 -5
- package/src/task/commands.ts +3 -1
- package/src/task/executor.ts +4 -0
- package/src/task/index.ts +6 -0
- package/src/task/isolation-dir.ts +143 -0
- package/src/task/worktree.ts +16 -7
- package/src/tools/ask.ts +205 -6
- package/src/tools/ast-edit-write-path.ts +68 -0
- package/src/tools/ast-edit.ts +10 -1
- package/src/tools/bash-allowed-prefixes.ts +15 -0
- package/src/tools/bash-skill-urls.ts +56 -3
- package/src/tools/bash.ts +48 -79
- package/src/tools/browser/cdp-url.ts +18 -0
- package/src/tools/browser.ts +8 -1
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/eval.ts +56 -36
- package/src/tools/gh.ts +10 -0
- package/src/tools/github-side-effect.ts +112 -0
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +3 -0
- package/src/tools/monitor-mutation.ts +27 -0
- package/src/tools/monitor.ts +7 -0
- package/src/tools/python.ts +188 -66
- package/src/tools/recipe/index.ts +9 -0
- package/src/tools/recipe/recipe-guard.ts +19 -0
- package/src/tools/restricted-cwd.ts +10 -0
- package/src/tools/skill-discovery.ts +5 -0
- package/src/tools/skill.ts +28 -1
- package/src/tools/todo-write.ts +10 -1
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/write.ts +1 -0
- package/src/utils/external-editor.ts +3 -2
- package/src/utils/tools-checksum.ts +34 -0
- package/src/utils/tools-manager.ts +24 -6
- package/src/web/insane/url-guard.ts +2 -1
- package/src/web/search/providers/codex.ts +3 -2
package/src/tools/python.ts
CHANGED
|
@@ -22,12 +22,20 @@ export function pythonKernelOwnerId(sessionId: string): string {
|
|
|
22
22
|
export interface SessionPythonToolInput {
|
|
23
23
|
/** Working directory for kernel execution (session cwd). */
|
|
24
24
|
cwd: string;
|
|
25
|
+
/** Resolve the current working directory for each admitted invocation. */
|
|
26
|
+
getCwd?: () => string;
|
|
25
27
|
/** Session settings used for Python runtime policy. */
|
|
26
28
|
settings?: SettingsType;
|
|
29
|
+
/** Resolve the session file associated with each admitted invocation. */
|
|
30
|
+
getSessionFile?: () => string | null;
|
|
27
31
|
/** Resolve the GJC session id used for the kernel owner and transcript paths. */
|
|
28
32
|
getSessionId: () => string | null;
|
|
29
33
|
/** Register cleanup with the current logical session lifecycle. */
|
|
30
|
-
registerSessionCleanup: (cleanup: () => Promise<void> | void) => void;
|
|
34
|
+
registerSessionCleanup: (cleanup: () => Promise<void> | void) => (() => void) | void;
|
|
35
|
+
/** Reject execution after the owning session has begun disposal. */
|
|
36
|
+
assertEvalExecutionAllowed?: () => void;
|
|
37
|
+
/** Track this whole invocation through its transcript append. */
|
|
38
|
+
trackEvalExecution?: <T>(execution: Promise<T>, abortController: AbortController) => Promise<T>;
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
const paramsSchema = z.object({
|
|
@@ -52,6 +60,25 @@ interface TranscriptExecutionResult {
|
|
|
52
60
|
truncated: boolean;
|
|
53
61
|
}
|
|
54
62
|
|
|
63
|
+
interface PythonGeneration {
|
|
64
|
+
readonly sessionId: string;
|
|
65
|
+
readonly ownerId: string;
|
|
66
|
+
readonly abortControllers: Set<AbortController>;
|
|
67
|
+
readonly completions: Set<Promise<void>>;
|
|
68
|
+
readonly transcripts: Map<string, PythonKernelTranscript>;
|
|
69
|
+
unregisterCleanup?: () => void;
|
|
70
|
+
cleanupPromise?: Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface PythonInvocationContext {
|
|
74
|
+
readonly cwd: string;
|
|
75
|
+
readonly sessionFile: string | null;
|
|
76
|
+
readonly sessionId: string;
|
|
77
|
+
readonly ownerId: string;
|
|
78
|
+
readonly settings: SettingsType;
|
|
79
|
+
readonly generation: PythonGeneration;
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
function errorMessage(error: unknown): string {
|
|
56
83
|
return error instanceof Error ? error.message : String(error);
|
|
57
84
|
}
|
|
@@ -71,33 +98,87 @@ function appendFailureTrailer(output: string, appendFailure: string | undefined)
|
|
|
71
98
|
}
|
|
72
99
|
|
|
73
100
|
export function createSessionPythonTool(input: SessionPythonToolInput): AgentTool {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
101
|
+
const activeGenerations = new Map<string, PythonGeneration>();
|
|
102
|
+
|
|
103
|
+
const transcriptFor = (
|
|
104
|
+
generation: PythonGeneration,
|
|
105
|
+
context: Pick<PythonInvocationContext, "cwd" | "sessionId">,
|
|
106
|
+
kernelInstanceId: string,
|
|
107
|
+
): PythonKernelTranscript => {
|
|
108
|
+
const key = JSON.stringify([context.cwd, context.sessionId, kernelInstanceId]);
|
|
109
|
+
let transcript = generation.transcripts.get(key);
|
|
110
|
+
if (!transcript) {
|
|
111
|
+
transcript = openPythonKernelTranscript({
|
|
112
|
+
cwd: context.cwd,
|
|
113
|
+
sessionId: context.sessionId,
|
|
114
|
+
kernelInstanceId,
|
|
115
|
+
});
|
|
116
|
+
generation.transcripts.set(key, transcript);
|
|
117
|
+
}
|
|
118
|
+
return transcript;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const retireGeneration = (generation: PythonGeneration): Promise<void> => {
|
|
122
|
+
if (generation.cleanupPromise) return generation.cleanupPromise;
|
|
123
|
+
const cleanup = Promise.withResolvers<void>();
|
|
124
|
+
generation.cleanupPromise = cleanup.promise;
|
|
125
|
+
const completions = [...generation.completions];
|
|
126
|
+
const controllers = [...generation.abortControllers];
|
|
127
|
+
if (activeGenerations.get(generation.sessionId) === generation) {
|
|
128
|
+
activeGenerations.delete(generation.sessionId);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let ownerCleanup: Promise<void>;
|
|
132
|
+
try {
|
|
133
|
+
// Core registers pending operations by this existing owner label before
|
|
134
|
+
// availability work begins, so this synchronous call also captures work
|
|
135
|
+
// that has not acquired a kernel yet.
|
|
136
|
+
ownerCleanup = disposeKernelSessionsByOwner(generation.ownerId);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
ownerCleanup = Promise.reject(error);
|
|
139
|
+
}
|
|
140
|
+
for (const controller of controllers) controller.abort();
|
|
141
|
+
void (async () => {
|
|
142
|
+
const results = await Promise.allSettled([ownerCleanup, ...completions]);
|
|
143
|
+
const coreResult = results[0]!;
|
|
144
|
+
if (coreResult.status === "rejected") throw coreResult.reason;
|
|
145
|
+
generation.unregisterCleanup?.();
|
|
146
|
+
generation.unregisterCleanup = undefined;
|
|
147
|
+
generation.transcripts.clear();
|
|
148
|
+
})().then(cleanup.resolve, error => {
|
|
149
|
+
if (generation.cleanupPromise === cleanup.promise) generation.cleanupPromise = undefined;
|
|
150
|
+
cleanup.reject(error);
|
|
85
151
|
});
|
|
86
|
-
|
|
152
|
+
return cleanup.promise;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const generationFor = (sessionId: string): PythonGeneration => {
|
|
156
|
+
const current = activeGenerations.get(sessionId);
|
|
157
|
+
if (current) return current;
|
|
158
|
+
const generation: PythonGeneration = {
|
|
159
|
+
sessionId,
|
|
160
|
+
ownerId: pythonKernelOwnerId(sessionId),
|
|
161
|
+
abortControllers: new Set(),
|
|
162
|
+
completions: new Set(),
|
|
163
|
+
transcripts: new Map(),
|
|
164
|
+
};
|
|
165
|
+
activeGenerations.set(sessionId, generation);
|
|
166
|
+
try {
|
|
167
|
+
const unregister = input.registerSessionCleanup(() => retireGeneration(generation));
|
|
168
|
+
if (typeof unregister === "function") generation.unregisterCleanup = unregister;
|
|
169
|
+
} catch (error) {
|
|
170
|
+
activeGenerations.delete(sessionId);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
return generation;
|
|
87
174
|
};
|
|
88
175
|
|
|
89
176
|
const appendTranscript = async (
|
|
90
|
-
|
|
177
|
+
context: PythonInvocationContext,
|
|
178
|
+
transcript: PythonKernelTranscript | undefined,
|
|
91
179
|
code: string,
|
|
92
180
|
result: TranscriptExecutionResult,
|
|
93
181
|
): Promise<string | undefined> => {
|
|
94
|
-
if (currentTranscript === null) {
|
|
95
|
-
currentTranscript = openPythonKernelTranscript({
|
|
96
|
-
cwd: input.cwd,
|
|
97
|
-
sessionId,
|
|
98
|
-
kernelInstanceId: crypto.randomUUID(),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
182
|
const record: PythonTranscriptRecord = {
|
|
102
183
|
timestamp: new Date().toISOString(),
|
|
103
184
|
code,
|
|
@@ -107,11 +188,12 @@ export function createSessionPythonTool(input: SessionPythonToolInput): AgentToo
|
|
|
107
188
|
truncated: result.truncated,
|
|
108
189
|
};
|
|
109
190
|
try {
|
|
110
|
-
|
|
191
|
+
const target = transcript ?? transcriptFor(context.generation, context, crypto.randomUUID());
|
|
192
|
+
await target.append(record);
|
|
111
193
|
return undefined;
|
|
112
194
|
} catch (error) {
|
|
113
195
|
const message = errorMessage(error);
|
|
114
|
-
logger.warn("Python transcript append failed", { sessionId, error: message });
|
|
196
|
+
logger.warn("Python transcript append failed", { sessionId: context.sessionId, error: message });
|
|
115
197
|
return message;
|
|
116
198
|
}
|
|
117
199
|
};
|
|
@@ -135,11 +217,9 @@ export function createSessionPythonTool(input: SessionPythonToolInput): AgentToo
|
|
|
135
217
|
isError: true,
|
|
136
218
|
};
|
|
137
219
|
}
|
|
138
|
-
armCleanupForSession(sessionId);
|
|
139
|
-
const ownerId = pythonKernelOwnerId(sessionId);
|
|
140
220
|
if (params.action === "clear") {
|
|
141
|
-
|
|
142
|
-
|
|
221
|
+
const generation = generationFor(sessionId);
|
|
222
|
+
await retireGeneration(generation);
|
|
143
223
|
return {
|
|
144
224
|
content: [{ type: "text", text: "Python kernel cleared; the next execute starts a fresh kernel." }],
|
|
145
225
|
};
|
|
@@ -152,48 +232,90 @@ export function createSessionPythonTool(input: SessionPythonToolInput): AgentToo
|
|
|
152
232
|
};
|
|
153
233
|
}
|
|
154
234
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
235
|
+
const cwd = input.getCwd?.() ?? input.cwd;
|
|
236
|
+
const sessionFile = input.getSessionFile?.() ?? null;
|
|
237
|
+
const settings = input.settings ?? Settings.instance;
|
|
238
|
+
input.assertEvalExecutionAllowed?.();
|
|
239
|
+
const contextGeneration = generationFor(sessionId);
|
|
240
|
+
const context: PythonInvocationContext = {
|
|
241
|
+
cwd,
|
|
242
|
+
sessionFile,
|
|
243
|
+
sessionId,
|
|
244
|
+
ownerId: contextGeneration.ownerId,
|
|
245
|
+
settings,
|
|
246
|
+
generation: contextGeneration,
|
|
247
|
+
};
|
|
248
|
+
const abortController = new AbortController();
|
|
249
|
+
const abortFromCaller = (): void => abortController.abort(signal?.reason);
|
|
250
|
+
if (signal?.aborted) abortFromCaller();
|
|
251
|
+
else signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
252
|
+
contextGeneration.abortControllers.add(abortController);
|
|
253
|
+
|
|
254
|
+
let trackingAccepted = false;
|
|
255
|
+
const execution = Promise.resolve()
|
|
256
|
+
.then(async (): Promise<AgentToolResult> => {
|
|
257
|
+
if (!trackingAccepted) throw new Error("Python execution was not admitted by its owning session.");
|
|
258
|
+
input.assertEvalExecutionAllowed?.();
|
|
259
|
+
let transcript: PythonKernelTranscript | undefined;
|
|
260
|
+
try {
|
|
261
|
+
const result = await executePython(code, {
|
|
262
|
+
cwd: context.cwd,
|
|
263
|
+
settings: context.settings,
|
|
264
|
+
sessionFile: context.sessionFile ?? undefined,
|
|
265
|
+
kernelMode: "session",
|
|
266
|
+
sessionId: context.ownerId,
|
|
267
|
+
kernelOwnerId: context.ownerId,
|
|
268
|
+
artifactsDir: sessionIpykernelsArtifactsDir(context.cwd, context.sessionId),
|
|
269
|
+
signal: abortController.signal,
|
|
270
|
+
onKernelStart: kernelInstanceId => {
|
|
271
|
+
transcript = transcriptFor(context.generation, context, kernelInstanceId);
|
|
272
|
+
},
|
|
273
|
+
});
|
|
274
|
+
const appendFailure = await appendTranscript(context, transcript, code, {
|
|
275
|
+
output: result.output,
|
|
276
|
+
exitCode: result.exitCode ?? null,
|
|
277
|
+
cancelled: result.cancelled,
|
|
278
|
+
truncated: result.truncated,
|
|
279
|
+
});
|
|
280
|
+
const output = result.output.length > 0 ? result.output : "(no output)";
|
|
281
|
+
return { content: [{ type: "text", text: appendFailureTrailer(output, appendFailure) }] };
|
|
282
|
+
} catch (error) {
|
|
283
|
+
const output = errorMessage(error);
|
|
284
|
+
const appendFailure = await appendTranscript(context, transcript, code, {
|
|
285
|
+
output,
|
|
286
|
+
exitCode: null,
|
|
287
|
+
cancelled: isCancellationError(error, abortController.signal),
|
|
288
|
+
truncated: false,
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
content: [{ type: "text", text: appendFailureTrailer(output, appendFailure) }],
|
|
292
|
+
isError: true,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
.finally(() => {
|
|
297
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
298
|
+
contextGeneration.abortControllers.delete(abortController);
|
|
181
299
|
});
|
|
182
|
-
|
|
183
|
-
|
|
300
|
+
const settled = execution.then(
|
|
301
|
+
() => {},
|
|
302
|
+
() => {},
|
|
303
|
+
);
|
|
304
|
+
contextGeneration.completions.add(settled);
|
|
305
|
+
void settled.then(() => contextGeneration.completions.delete(settled));
|
|
306
|
+
|
|
307
|
+
let completion: Promise<AgentToolResult>;
|
|
308
|
+
try {
|
|
309
|
+
completion = input.trackEvalExecution ? input.trackEvalExecution(execution, abortController) : execution;
|
|
310
|
+
trackingAccepted = true;
|
|
184
311
|
} catch (error) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
truncated: false,
|
|
191
|
-
});
|
|
192
|
-
return {
|
|
193
|
-
content: [{ type: "text", text: appendFailureTrailer(output, appendFailure) }],
|
|
194
|
-
isError: true,
|
|
195
|
-
};
|
|
312
|
+
abortController.abort(error);
|
|
313
|
+
signal?.removeEventListener("abort", abortFromCaller);
|
|
314
|
+
contextGeneration.abortControllers.delete(abortController);
|
|
315
|
+
void execution.catch(() => {});
|
|
316
|
+
throw error;
|
|
196
317
|
}
|
|
318
|
+
return await completion;
|
|
197
319
|
},
|
|
198
320
|
};
|
|
199
321
|
const agentTool = {
|
|
@@ -7,6 +7,7 @@ import type { Theme } from "../../modes/theme/theme";
|
|
|
7
7
|
import recipeDescription from "../../prompts/tools/recipe.md" with { type: "text" };
|
|
8
8
|
import type { ToolSession } from "..";
|
|
9
9
|
import { type BashRenderContext, BashTool, type BashToolDetails } from "../bash";
|
|
10
|
+
import { assertRecipeCommandAllowed } from "./recipe-guard";
|
|
10
11
|
import { createRecipeToolRenderer, type RecipeRenderArgs } from "./render";
|
|
11
12
|
import { buildPromptModel, type DetectedRunner, resolveCommand } from "./runner";
|
|
12
13
|
import { RUNNERS } from "./runners";
|
|
@@ -48,8 +49,10 @@ export class RecipeTool implements AgentTool<typeof recipeSchema, BashToolDetail
|
|
|
48
49
|
|
|
49
50
|
readonly #bash: BashTool;
|
|
50
51
|
readonly #runners: DetectedRunner[];
|
|
52
|
+
readonly #session: ToolSession;
|
|
51
53
|
|
|
52
54
|
constructor(session: ToolSession, runners: DetectedRunner[]) {
|
|
55
|
+
this.#session = session;
|
|
53
56
|
this.#runners = runners;
|
|
54
57
|
this.#bash = new BashTool(session);
|
|
55
58
|
this.description = prompt.render(recipeDescription, buildPromptModel(runners));
|
|
@@ -75,6 +78,12 @@ export class RecipeTool implements AgentTool<typeof recipeSchema, BashToolDetail
|
|
|
75
78
|
ctx?: AgentToolContext,
|
|
76
79
|
): Promise<AgentToolResult<BashToolDetails>> {
|
|
77
80
|
const { command, cwd } = resolveCommand(op, this.#runners);
|
|
81
|
+
await assertRecipeCommandAllowed({
|
|
82
|
+
cwd: this.#session.cwd,
|
|
83
|
+
sessionId: this.#session.getSessionId?.() ?? undefined,
|
|
84
|
+
command,
|
|
85
|
+
commandCwd: cwd,
|
|
86
|
+
});
|
|
78
87
|
return await this.#bash.execute(toolCallId, { command, cwd }, signal, onUpdate, ctx);
|
|
79
88
|
}
|
|
80
89
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AgentTool } from "@gajae-code/agent-core";
|
|
2
|
+
import { assertWorkflowMutationAllowed } from "../../skill-state/workflow-mutation-guard";
|
|
3
|
+
|
|
4
|
+
const BASH_GUARD_TOOL = { name: "bash" } as AgentTool;
|
|
5
|
+
|
|
6
|
+
/** Apply the session bash planning guard to a recipe command before it runs. */
|
|
7
|
+
export async function assertRecipeCommandAllowed(input: {
|
|
8
|
+
cwd: string;
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
command: string;
|
|
11
|
+
commandCwd?: string;
|
|
12
|
+
}): Promise<void> {
|
|
13
|
+
await assertWorkflowMutationAllowed({
|
|
14
|
+
cwd: input.cwd,
|
|
15
|
+
sessionId: input.sessionId,
|
|
16
|
+
tool: BASH_GUARD_TOOL,
|
|
17
|
+
args: { command: input.command, cwd: input.commandCwd },
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export async function assertCwdInsideWorkspace(workspace: string, commandCwd: string): Promise<void> {
|
|
5
|
+
const realWorkspace = await fs.realpath(workspace);
|
|
6
|
+
const realCwd = await fs.realpath(commandCwd);
|
|
7
|
+
if (realCwd !== realWorkspace && !realCwd.startsWith(`${realWorkspace}${path.sep}`)) {
|
|
8
|
+
throw new Error("restricted bash cwd must stay inside the workspace");
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
2
2
|
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
3
3
|
import * as z from "zod/v4";
|
|
4
|
+
import { resolveGlobalUserSkillLinkTrust } from "../config/skill-settings-defaults";
|
|
4
5
|
import {
|
|
5
6
|
describeDisabledSkillScopes,
|
|
6
7
|
describeNoSkillMatch,
|
|
@@ -93,6 +94,10 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
|
|
|
93
94
|
source,
|
|
94
95
|
limit: input.limit,
|
|
95
96
|
policy: this.#getRuntimeSkillPolicy(),
|
|
97
|
+
allowExternalUserSkillSymlinks: resolveGlobalUserSkillLinkTrust({
|
|
98
|
+
trustUserSkills: this.#session.settings.getGlobal("skills.trustUserSkills"),
|
|
99
|
+
enablePiUser: this.#session.settings.getGlobal("skills.enablePiUser"),
|
|
100
|
+
}),
|
|
96
101
|
});
|
|
97
102
|
const details: SkillDiscoveryToolDetails = {
|
|
98
103
|
candidates: result.candidates,
|
package/src/tools/skill.ts
CHANGED
|
@@ -24,10 +24,15 @@
|
|
|
24
24
|
import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
|
|
25
25
|
import { prompt, untilAborted } from "@gajae-code/utils";
|
|
26
26
|
import * as z from "zod/v4";
|
|
27
|
+
import { resolveGlobalUserSkillLinkTrust } from "../config/skill-settings-defaults";
|
|
27
28
|
import { resolveSubskillActivationForSkillInvocation } from "../extensibility/gjc-plugins";
|
|
28
29
|
import { findRuntimeSkillByName } from "../extensibility/runtime-skill-discovery";
|
|
29
30
|
import { buildSkillPromptMessage } from "../extensibility/skills";
|
|
30
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
executionApprovalLineage,
|
|
33
|
+
hasSanctionedRalplanFinalAdmissionForHandoff,
|
|
34
|
+
runNativeStateCommand,
|
|
35
|
+
} from "../gjc-runtime/state-runtime";
|
|
31
36
|
import { getSkillManifest } from "../gjc-runtime/workflow-manifest";
|
|
32
37
|
import skillDescription from "../prompts/tools/skill.md" with { type: "text" };
|
|
33
38
|
import { SKILL_PROMPT_MESSAGE_TYPE } from "../session/messages";
|
|
@@ -187,6 +192,10 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
187
192
|
this.#session.home,
|
|
188
193
|
agentDir,
|
|
189
194
|
this.#session.profileAuthority,
|
|
195
|
+
resolveGlobalUserSkillLinkTrust({
|
|
196
|
+
trustUserSkills: this.#session.settings.getGlobal("skills.trustUserSkills"),
|
|
197
|
+
enablePiUser: this.#session.settings.getGlobal("skills.enablePiUser"),
|
|
198
|
+
}),
|
|
190
199
|
));
|
|
191
200
|
if (!skill) {
|
|
192
201
|
const available = formatAvailableSkills(skills);
|
|
@@ -209,6 +218,24 @@ export class SkillTool implements AgentTool<typeof skillSchema, SkillToolDetails
|
|
|
209
218
|
}
|
|
210
219
|
const cwd = this.#session.cwd;
|
|
211
220
|
const sessionId = activeState?.session_id?.trim();
|
|
221
|
+
if (activeSkill === "ralplan" && requestedName === "ultragoal") {
|
|
222
|
+
if (!sessionId) {
|
|
223
|
+
throw new ToolError("skill tool: Ralplan to Ultragoal handoff requires a session id");
|
|
224
|
+
}
|
|
225
|
+
if (!(await hasSanctionedRalplanFinalAdmissionForHandoff(this.#session.cwd, sessionId))) {
|
|
226
|
+
const lineage = await executionApprovalLineage(this.#session.cwd, sessionId, "ralplan");
|
|
227
|
+
const approvalMode = lineage === "crystal" ? "deep-interview" : "ralplan";
|
|
228
|
+
const approval = await runNativeStateCommand(
|
|
229
|
+
["approve-execution", "--mode", approvalMode, "--session-id", sessionId, "--json"],
|
|
230
|
+
this.#session.cwd,
|
|
231
|
+
);
|
|
232
|
+
if (approval.status !== 0) {
|
|
233
|
+
throw new ToolError(
|
|
234
|
+
`skill tool: Ralplan execution approval failed (status=${approval.status}): ${(approval.stderr ?? "").trim() || "no detail"}`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
212
239
|
const handoffArgs = ["handoff", "--mode", activeSkill, "--to", requestedName, "--json"];
|
|
213
240
|
if (sessionId) {
|
|
214
241
|
handoffArgs.push("--session-id", sessionId);
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -135,6 +135,15 @@ function normalizeInProgressTask(phases: TodoPhase[]): void {
|
|
|
135
135
|
export const USER_TODO_EDIT_CUSTOM_TYPE = "user_todo_edit";
|
|
136
136
|
|
|
137
137
|
export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPhase[] {
|
|
138
|
+
return findLatestTodoPhasesFromEntries(entries) ?? [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Latest persisted todo list on the branch, terminal items included. `undefined`
|
|
143
|
+
* means no todo state was ever persisted; an empty array is an authoritative
|
|
144
|
+
* empty list (for example after a bare `/todo rm`).
|
|
145
|
+
*/
|
|
146
|
+
export function findLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPhase[] | undefined {
|
|
138
147
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
139
148
|
const entry = entries[i];
|
|
140
149
|
if (entry.type === "custom" && entry.customType === USER_TODO_EDIT_CUSTOM_TYPE) {
|
|
@@ -154,7 +163,7 @@ export function getLatestTodoPhasesFromEntries(entries: SessionEntry[]): TodoPha
|
|
|
154
163
|
return clonePhases(details.phases as TodoPhase[]);
|
|
155
164
|
}
|
|
156
165
|
|
|
157
|
-
return
|
|
166
|
+
return undefined;
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
function resolveTaskOrError(
|
|
@@ -2783,7 +2783,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
|
|
|
2783
2783
|
"skill_discovery": {
|
|
2784
2784
|
"name": "skill_discovery",
|
|
2785
2785
|
"label": "SkillDiscovery",
|
|
2786
|
-
"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`).
|
|
2786
|
+
"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`). Project link and target identities remain checked through body loading; external project targets are refused. An individual user-scope skill symlink inside `~/.gjc/agent/skills` or a configured custom directory may resolve outside its scan root when user/global `skills.trustUserSkills` is enabled; project settings cannot grant this permission. Its link and target identities remain checked through body loading. Symlinked user/profile authority roots remain 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.",
|
|
2787
2787
|
"parameters": {
|
|
2788
2788
|
"type": "object",
|
|
2789
2789
|
"properties": {
|
package/src/tools/write.ts
CHANGED
|
@@ -462,6 +462,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
462
462
|
context: AgentToolContext | undefined,
|
|
463
463
|
): Promise<AgentToolResult<WriteToolDetails>> {
|
|
464
464
|
const absolutePath = entry.absolutePath;
|
|
465
|
+
enforcePlanModeWrite(this.session, absolutePath, { op: "update" });
|
|
465
466
|
if (!(await fs.exists(absolutePath))) {
|
|
466
467
|
throw new ToolError(`Conflict #${entry.id} target '${entry.displayPath}' no longer exists.`);
|
|
467
468
|
}
|
|
@@ -6,11 +6,12 @@ import { spawn } from "node:child_process";
|
|
|
6
6
|
import * as fs from "node:fs/promises";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { $
|
|
9
|
+
import { $pickCredentialEnv, Snowflake } from "@gajae-code/utils";
|
|
10
10
|
|
|
11
11
|
/** Returns the user's preferred editor command, or undefined if not configured. */
|
|
12
12
|
export function getEditorCommand(): string | undefined {
|
|
13
|
-
|
|
13
|
+
// The editor is executed, so a repository `.env` must not be able to choose it.
|
|
14
|
+
return $pickCredentialEnv("VISUAL", "EDITOR");
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface OpenInEditorOptions {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
const SHA256_DIGEST_RE = /^sha256:([a-fA-F0-9]{64})$/;
|
|
5
|
+
|
|
6
|
+
export interface ReleaseAssetDigest {
|
|
7
|
+
name?: string;
|
|
8
|
+
digest?: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function sha256FromReleaseDigest(digest: string | null | undefined): string | null {
|
|
12
|
+
if (typeof digest !== "string") return null;
|
|
13
|
+
const match = SHA256_DIGEST_RE.exec(digest.trim());
|
|
14
|
+
return match ? match[1].toLowerCase() : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function requireAssetSha256(assets: readonly ReleaseAssetDigest[], assetName: string): string {
|
|
18
|
+
const asset = assets.find(entry => entry.name === assetName);
|
|
19
|
+
const sha256 = sha256FromReleaseDigest(asset?.digest);
|
|
20
|
+
if (!sha256) {
|
|
21
|
+
throw new Error(`Release asset ${assetName} has no sha256 digest; refusing to install an unverified binary`);
|
|
22
|
+
}
|
|
23
|
+
return sha256;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function assertFileMatchesSha256(filePath: string, expectedSha256: string): Promise<void> {
|
|
27
|
+
const actual = createHash("sha256")
|
|
28
|
+
.update(await fs.readFile(filePath))
|
|
29
|
+
.digest("hex");
|
|
30
|
+
if (actual !== expectedSha256.toLowerCase()) {
|
|
31
|
+
await fs.rm(filePath, { force: true });
|
|
32
|
+
throw new Error(`Checksum mismatch for ${filePath}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { $which, APP_NAME, getToolsDir, logger, ptree, TempDir } from "@gajae-code/utils";
|
|
5
|
+
import { assertFileMatchesSha256, requireAssetSha256 } from "./tools-checksum";
|
|
5
6
|
|
|
6
7
|
const TOOLS_DIR = getToolsDir();
|
|
7
8
|
const TOOL_DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
@@ -112,8 +113,18 @@ export function getToolPath(tool: ToolName): string | null {
|
|
|
112
113
|
return $which(config.binaryName);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
interface GithubReleaseAsset {
|
|
117
|
+
name?: string;
|
|
118
|
+
digest?: string | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface GithubRelease {
|
|
122
|
+
version: string;
|
|
123
|
+
assets: GithubReleaseAsset[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fetch latest release metadata from GitHub, including asset digests.
|
|
127
|
+
async function getLatestRelease(repo: string, signal?: AbortSignal): Promise<GithubRelease> {
|
|
117
128
|
let response: Response;
|
|
118
129
|
try {
|
|
119
130
|
response = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
@@ -131,8 +142,11 @@ async function getLatestVersion(repo: string, signal?: AbortSignal): Promise<str
|
|
|
131
142
|
throw new Error(`GitHub API error: ${response.status}`);
|
|
132
143
|
}
|
|
133
144
|
|
|
134
|
-
const data = (await response.json()) as { tag_name
|
|
135
|
-
|
|
145
|
+
const data = (await response.json()) as { tag_name?: string; assets?: GithubReleaseAsset[] };
|
|
146
|
+
if (!data.tag_name) {
|
|
147
|
+
throw new Error("GitHub API error: missing tag_name");
|
|
148
|
+
}
|
|
149
|
+
return { version: data.tag_name.replace(/^v/, ""), assets: data.assets ?? [] };
|
|
136
150
|
}
|
|
137
151
|
|
|
138
152
|
// Download a file from URL
|
|
@@ -164,14 +178,16 @@ async function downloadTool(tool: ToolName, signal?: AbortSignal): Promise<strin
|
|
|
164
178
|
const plat = os.platform();
|
|
165
179
|
const architecture = os.arch();
|
|
166
180
|
|
|
167
|
-
// Get latest version
|
|
168
|
-
const
|
|
181
|
+
// Get latest version and the published digest for this asset.
|
|
182
|
+
const release = await getLatestRelease(config.repo, signal);
|
|
183
|
+
const version = release.version;
|
|
169
184
|
|
|
170
185
|
// Get asset name for this platform
|
|
171
186
|
const assetName = config.getAssetName(version, plat, architecture);
|
|
172
187
|
if (!assetName) {
|
|
173
188
|
throw new Error(`Unsupported platform: ${plat}/${architecture}`);
|
|
174
189
|
}
|
|
190
|
+
const expectedSha256 = requireAssetSha256(release.assets, assetName);
|
|
175
191
|
|
|
176
192
|
// Create tools directory
|
|
177
193
|
await fs.promises.mkdir(TOOLS_DIR, { recursive: true });
|
|
@@ -183,6 +199,7 @@ async function downloadTool(tool: ToolName, signal?: AbortSignal): Promise<strin
|
|
|
183
199
|
// Handle direct binary downloads (no archive extraction needed)
|
|
184
200
|
if (config.isDirectBinary) {
|
|
185
201
|
await downloadFile(downloadUrl, binaryPath, signal);
|
|
202
|
+
await assertFileMatchesSha256(binaryPath, expectedSha256);
|
|
186
203
|
if (plat !== "win32") {
|
|
187
204
|
await fs.promises.chmod(binaryPath, 0o755);
|
|
188
205
|
}
|
|
@@ -192,6 +209,7 @@ async function downloadTool(tool: ToolName, signal?: AbortSignal): Promise<strin
|
|
|
192
209
|
// Download archive
|
|
193
210
|
const archivePath = path.join(TOOLS_DIR, assetName);
|
|
194
211
|
await downloadFile(downloadUrl, archivePath, signal);
|
|
212
|
+
await assertFileMatchesSha256(archivePath, expectedSha256);
|
|
195
213
|
|
|
196
214
|
// Extract
|
|
197
215
|
const tmp = await TempDir.create("@gjc-tools-extract-");
|
|
@@ -74,7 +74,7 @@ function isBlockedHostname(hostname: string): boolean {
|
|
|
74
74
|
function isPrivateIPv4(address: string): boolean {
|
|
75
75
|
const parts = address.split(".").map(part => Number.parseInt(part, 10));
|
|
76
76
|
if (parts.length !== 4 || parts.some(part => !Number.isInteger(part) || part < 0 || part > 255)) return true;
|
|
77
|
-
const [a, b] = parts;
|
|
77
|
+
const [a, b, c] = parts;
|
|
78
78
|
return (
|
|
79
79
|
a === 0 || // unspecified / "this network"
|
|
80
80
|
a === 10 || // RFC1918
|
|
@@ -83,6 +83,7 @@ function isPrivateIPv4(address: string): boolean {
|
|
|
83
83
|
(a === 169 && b === 254) || // link-local
|
|
84
84
|
(a === 172 && b >= 16 && b <= 31) || // RFC1918
|
|
85
85
|
(a === 192 && b === 0) || // 192.0.0/24 & 192.0.2/24 (documentation/reserved)
|
|
86
|
+
(a === 192 && b === 88 && c === 99) || // 192.88.99.0/24 deprecated 6to4 anycast; 192.88.99.2/32 is not globally reachable
|
|
86
87
|
(a === 192 && b === 168) || // RFC1918
|
|
87
88
|
(a === 198 && (b === 18 || b === 19)) || // benchmarking 198.18/15
|
|
88
89
|
(a === 198 && b === 51) || // 198.51.100/24 documentation
|