@gajae-code/coding-agent 0.14.2 → 0.15.0
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 +120 -1
- package/dist/types/async/job-manager.d.ts +4 -1
- package/dist/types/autoresearch/dashboard.d.ts +29 -4
- package/dist/types/autoresearch/data-context.d.ts +25 -0
- package/dist/types/autoresearch/git.d.ts +65 -7
- package/dist/types/autoresearch/harness.d.ts +58 -0
- package/dist/types/autoresearch/prompts.d.ts +51 -0
- package/dist/types/autoresearch/runs.d.ts +173 -0
- package/dist/types/autoresearch/types.d.ts +9 -139
- package/dist/types/capability/types.d.ts +20 -0
- package/dist/types/cli/accounts-cli.d.ts +1 -0
- package/dist/types/commands/{rlm.d.ts → autoresearch.d.ts} +2 -4
- package/dist/types/config/autorouting-contract.d.ts +146 -0
- package/dist/types/config/autorouting-generator.d.ts +40 -0
- package/dist/types/config/autorouting-tier-map.d.ts +16844 -0
- package/dist/types/config/autorouting.d.ts +35 -0
- package/dist/types/config/model-discovery-manager.d.ts +8 -1
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/provider-selection-policy.d.ts +23 -0
- package/dist/types/config/settings-schema.d.ts +61 -3
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/durability.d.ts +29 -0
- package/dist/types/coordinator-mcp/event-webhook.d.ts +50 -0
- package/dist/types/coordinator-mcp/question-state.d.ts +1 -1
- package/dist/types/coordinator-mcp/server.d.ts +14 -0
- package/dist/types/crash/index-store.d.ts +3 -0
- package/dist/types/crash/record-loader.d.ts +2 -0
- package/dist/types/crash/upstream/relay.d.ts +5 -3
- package/dist/types/cursor.d.ts +12 -0
- package/dist/types/defaults/gjc-defaults.d.ts +35 -1
- package/dist/types/defaults/gjc-skills.generated.d.ts +1 -1
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/loader.d.ts +32 -0
- package/dist/types/extensibility/gjc-plugins/types.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +2 -0
- package/dist/types/gjc-runtime/autoresearch-runtime.d.ts +194 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/psmux-detect.d.ts +1 -1
- package/dist/types/gjc-runtime/python-transcript.d.ts +19 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +2 -8
- package/dist/types/gjc-runtime/session-layout.d.ts +6 -3
- package/dist/types/gjc-runtime/state-renderer.d.ts +0 -22
- package/dist/types/gjc-runtime/state-schema.d.ts +7 -7
- package/dist/types/gjc-runtime/tmux-common.d.ts +2 -2
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +38 -1
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -24
- package/dist/types/gjc-runtime/ultragoal-validation-policy.d.ts +73 -0
- package/dist/types/gjc-runtime/workflow-recovery-projection.d.ts +100 -0
- package/dist/types/hooks/skill-keywords.d.ts +1 -1
- package/dist/types/main.d.ts +0 -10
- package/dist/types/modes/acp/acp-agent.d.ts +13 -6
- package/dist/types/modes/components/model-selector.d.ts +14 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +6 -1
- package/dist/types/modes/components/smart-routing-panel.d.ts +82 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +14 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/types.d.ts +9 -0
- package/dist/types/rlm/artifacts.d.ts +4 -4
- package/dist/types/runtime/optional-runtime-services.d.ts +2 -1
- package/dist/types/runtime/workspace-tree-service.d.ts +5 -1
- package/dist/types/runtime-mcp/config.d.ts +6 -0
- package/dist/types/runtime-mcp/manager.d.ts +10 -0
- package/dist/types/sdk/acp/adapter.d.ts +0 -4
- package/dist/types/sdk/broker/broker.d.ts +1 -1
- package/dist/types/sdk/broker/lifecycle.d.ts +57 -2
- package/dist/types/sdk/broker/session-index.d.ts +15 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -0
- package/dist/types/sdk/bus/index.d.ts +8 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +12 -2
- package/dist/types/sdk/client/client.d.ts +39 -4
- package/dist/types/sdk/host/host.d.ts +1 -0
- package/dist/types/sdk/host/internal-autorouting-state.d.ts +3 -0
- package/dist/types/sdk/host/model-pin.d.ts +29 -0
- package/dist/types/sdk/host/session-runtime.d.ts +2 -1
- package/dist/types/sdk/lifecycle-session.d.ts +13 -0
- package/dist/types/sdk/router/session-router.d.ts +27 -1
- package/dist/types/sdk/session.d.ts +11 -1
- package/dist/types/session/agent-session.d.ts +36 -6
- package/dist/types/session/artifacts.d.ts +16 -1
- package/dist/types/session/internal/managed-session-storage.d.ts +7 -0
- package/dist/types/session/session-manager.d.ts +84 -2
- package/dist/types/session/session-staging-paths.d.ts +3 -0
- package/dist/types/skill-state/canonical-skills.d.ts +1 -1
- package/dist/types/skill-state/workflow-hud.d.ts +12 -21
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +1 -0
- package/dist/types/slash-commands/helpers/autorouting-status.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +23 -0
- package/dist/types/task/executor.d.ts +35 -4
- package/dist/types/task/index.d.ts +17 -3
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +144 -0
- package/dist/types/tools/atomic-file-write.d.ts +72 -0
- package/dist/types/tools/descriptors.d.ts +1 -0
- package/dist/types/tools/index.d.ts +36 -1
- package/dist/types/tools/move-session.d.ts +40 -0
- package/dist/types/tools/python.d.ts +12 -0
- package/dist/types/tools/resource-gc.d.ts +0 -3
- package/dist/types/tools/search.d.ts +8 -1
- package/dist/types/tools/skill.d.ts +9 -4
- package/dist/types/tools/subagent.d.ts +33 -3
- package/dist/types/utils/xml-escape.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +2 -2
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +1 -1
- package/package.json +11 -12
- package/scripts/build-sdk-package-smoke.ts +7 -5
- package/scripts/check-autorouting-tier-map.ts +183 -0
- package/scripts/generate-sdk-operation-inventory.ts +12 -0
- package/scripts/generate-tool-catalog.ts +40 -5
- package/src/async/job-manager.ts +5 -2
- package/src/autoresearch/dashboard.ts +134 -314
- package/src/autoresearch/data-context.ts +35 -0
- package/src/autoresearch/git.ts +186 -26
- package/src/autoresearch/{helpers.ts → harness.ts} +65 -40
- package/src/autoresearch/{prompt-setup.md → prompts/prompt-setup.md} +7 -7
- package/src/autoresearch/prompts/prompt.md +103 -0
- package/src/autoresearch/prompts.ts +126 -0
- package/src/autoresearch/runs.ts +708 -0
- package/src/autoresearch/types.ts +9 -150
- package/src/capability/index.ts +4 -4
- package/src/capability/types.ts +20 -0
- package/src/cli/accounts-cli.ts +21 -5
- package/src/cli/config-cli.ts +25 -1
- package/src/cli/fast-help.ts +1 -2
- package/src/cli/list-models.ts +5 -8
- package/src/cli/setup-cli.ts +8 -0
- package/src/cli.ts +2 -21
- package/src/commands/autoresearch.ts +33 -0
- package/src/commands/coordinator.ts +15 -0
- package/src/commands/launch.ts +2 -4
- package/src/commands/quick-lane.ts +1 -1
- package/src/commands/sdk.ts +24 -0
- package/src/commands/state.ts +1 -1
- package/src/config/autorouting-contract.ts +474 -0
- package/src/config/autorouting-generator.ts +238 -0
- package/src/config/autorouting-tier-map.ts +6108 -0
- package/src/config/autorouting.ts +120 -0
- package/src/config/model-discovery-manager.ts +42 -11
- package/src/config/model-registry.ts +348 -68
- package/src/config/provider-selection-policy.ts +60 -9
- package/src/config/settings-schema.ts +195 -6
- package/src/config/settings.ts +62 -1
- package/src/config.ts +2 -3
- package/src/coordinator/contract.ts +0 -1
- package/src/coordinator-mcp/codex-handoff.ts +68 -42
- package/src/coordinator-mcp/durability.ts +200 -0
- package/src/coordinator-mcp/event-webhook.ts +278 -0
- package/src/coordinator-mcp/question-state.ts +109 -46
- package/src/coordinator-mcp/server.ts +932 -504
- package/src/crash/index-store.ts +126 -22
- package/src/crash/record-loader.ts +7 -0
- package/src/crash/upstream/relay.ts +82 -34
- package/src/cursor.ts +84 -1
- package/src/debug/index.ts +26 -0
- package/src/defaults/gjc/skills/autoresearch/SKILL.md +116 -0
- package/src/defaults/gjc/skills/autoresearch/auto-critic.md +46 -0
- package/src/defaults/gjc/skills/autoresearch/auto-iterate.md +42 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +17 -37
- package/src/defaults/gjc/skills/ralplan/SKILL.md +21 -18
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +6 -17
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +2 -2
- package/src/defaults/gjc-defaults.ts +89 -2
- package/src/defaults/gjc-skills.generated.ts +24 -17
- package/src/discovery/builtin.ts +18 -5
- package/src/discovery/helpers.ts +6 -6
- package/src/edit/index.ts +5 -2
- package/src/eval/py/executor.ts +8 -0
- package/src/extensibility/extensions/loader.ts +135 -13
- package/src/extensibility/extensions/wrapper.ts +1 -2
- package/src/extensibility/gjc-plugins/schema.ts +1 -1
- package/src/extensibility/runtime-skill-discovery.ts +2 -2
- package/src/extensibility/skill-management.ts +2 -3
- package/src/extensibility/skills.ts +16 -8
- package/src/gjc-runtime/autoresearch-runtime.ts +1509 -0
- package/src/gjc-runtime/gc-render.ts +0 -1
- package/src/gjc-runtime/gc-runtime.ts +1 -11
- package/src/gjc-runtime/launch-tmux.ts +3 -3
- package/src/gjc-runtime/psmux-detect.ts +2 -2
- package/src/gjc-runtime/python-transcript.ts +58 -0
- package/src/gjc-runtime/ralplan-runtime.ts +6 -18
- package/src/gjc-runtime/session-layout.ts +18 -7
- package/src/gjc-runtime/session-state-sidecar.ts +58 -9
- package/src/gjc-runtime/state-renderer.ts +0 -38
- package/src/gjc-runtime/state-runtime.ts +43 -22
- package/src/gjc-runtime/state-schema.ts +1 -1
- package/src/gjc-runtime/tmux-common.ts +7 -7
- package/src/gjc-runtime/ultragoal-change-set.ts +232 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +309 -114
- package/src/gjc-runtime/ultragoal-validation-policy.ts +247 -0
- package/src/gjc-runtime/workflow-command-ref.ts +15 -11
- package/src/gjc-runtime/workflow-manifest.generated.json +413 -532
- package/src/gjc-runtime/workflow-manifest.ts +43 -83
- package/src/gjc-runtime/workflow-recovery-projection.ts +616 -0
- package/src/hooks/skill-keywords.ts +3 -3
- package/src/hooks/skill-state.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +26 -24
- package/src/lsp/index.ts +25 -5
- package/src/main.ts +2 -27
- package/src/modes/acp/acp-agent.ts +200 -75
- package/src/modes/components/model-selector.ts +216 -13
- package/src/modes/components/oauth-selector.ts +5 -5
- package/src/modes/components/skill-hud/render.ts +173 -35
- package/src/modes/components/smart-routing-panel.ts +531 -0
- package/src/modes/components/tool-status-header.ts +6 -3
- package/src/modes/controllers/command-controller.ts +17 -7
- package/src/modes/controllers/input-controller.ts +44 -9
- package/src/modes/controllers/runtime-mcp-command-controller.ts +4 -0
- package/src/modes/controllers/selector-controller.ts +298 -65
- package/src/modes/interactive-mode.ts +8 -1
- package/src/modes/shared/agent-wire/workflow-gate-types.ts +1 -1
- package/src/modes/types.ts +9 -1
- package/src/prompts/agents/planner.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/volatile-project-context.md +2 -1
- package/src/prompts/tools/move-session.md +15 -0
- package/src/prompts/tools/python.md +12 -0
- package/src/prompts/tools/skill-discovery.md +1 -1
- package/src/prompts/tools/skill.md +2 -2
- package/src/prompts/tools/task-summary.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/rlm/artifacts.ts +12 -10
- package/src/runtime/optional-runtime-services.ts +3 -2
- package/src/runtime/workspace-tree-service.ts +11 -3
- package/src/runtime-mcp/config.ts +8 -1
- package/src/runtime-mcp/manager.ts +33 -0
- package/src/sdk/acp/adapter.ts +48 -1
- package/src/sdk/broker/broker.ts +30 -2
- package/src/sdk/broker/discovery.ts +217 -2
- package/src/sdk/broker/lifecycle.ts +385 -59
- package/src/sdk/broker/session-index.ts +243 -48
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/bus/chat-daemon-control.ts +8 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +5 -0
- package/src/sdk/bus/index.ts +208 -11
- package/src/sdk/bus/kind-aware-reconciliation.ts +16 -0
- package/src/sdk/bus/reconciliation-store.ts +27 -5
- package/src/sdk/bus/telegram-daemon-contract.ts +12 -2
- package/src/sdk/client/client.ts +204 -22
- package/src/sdk/host/host.ts +16 -0
- package/src/sdk/host/internal-autorouting-state.ts +13 -0
- package/src/sdk/host/model-pin.ts +101 -0
- package/src/sdk/host/reverse-leases.ts +2 -1
- package/src/sdk/host/session-runtime.test.ts +224 -1
- package/src/sdk/host/session-runtime.ts +125 -37
- package/src/sdk/lifecycle/service.ts +3 -1
- package/src/sdk/lifecycle-session.ts +42 -1
- package/src/sdk/protocol/operation-inventory.generated.json +66 -0
- package/src/sdk/router/session-router.ts +375 -36
- package/src/sdk/session.ts +547 -47
- package/src/session/account-inventory.ts +1 -1
- package/src/session/agent-session.ts +765 -189
- package/src/session/artifacts.ts +234 -4
- package/src/session/internal/managed-session-storage.ts +91 -4
- package/src/session/provider-safety-stop-hint.ts +3 -2
- package/src/session/session-manager.ts +775 -16
- package/src/session/session-staging-paths.ts +8 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +0 -1
- package/src/setup/host-plugin-setup.ts +2 -2
- package/src/skill-state/active-state.ts +2 -2
- package/src/skill-state/canonical-skills.ts +1 -1
- package/src/skill-state/initial-phase.ts +1 -1
- package/src/skill-state/workflow-hud.ts +41 -41
- package/src/skill-state/workflow-mutation-guard.ts +26 -7
- package/src/slash-commands/builtin-registry.ts +77 -0
- package/src/slash-commands/helpers/autorouting-status.ts +55 -0
- package/src/system-prompt.ts +102 -5
- package/src/task/discovery.ts +2 -3
- package/src/task/executor.ts +617 -43
- package/src/task/index.ts +262 -15
- package/src/task/receipt.ts +30 -0
- package/src/task/render.ts +11 -1
- package/src/task/types.ts +316 -0
- package/src/tools/atomic-file-write.ts +594 -0
- package/src/tools/bisect.ts +3 -2
- package/src/tools/descriptors.test.ts +3 -0
- package/src/tools/descriptors.ts +24 -0
- package/src/tools/image-gen.ts +5 -1
- package/src/tools/index.ts +62 -5
- package/src/tools/move-session.ts +107 -0
- package/src/tools/python.ts +205 -0
- package/src/tools/read.ts +88 -15
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resource-gc.ts +8 -177
- package/src/tools/search.ts +30 -6
- package/src/tools/skill-discovery.ts +1 -0
- package/src/tools/skill.ts +37 -7
- package/src/tools/subagent-render.ts +119 -13
- package/src/tools/subagent.ts +91 -74
- package/src/tools/tool-catalog.generated.ts +64 -2
- package/src/tools/write.ts +16 -12
- package/src/utils/xml-escape.ts +14 -0
- package/src/web/search/providers/codex.ts +2 -2
- package/src/workflow/workflow-intent-diff.ts +13 -12
- package/src/workflow/workflow-intent-report-consumer.ts +6 -6
- package/dist/types/autoresearch/helpers.d.ts +0 -24
- package/dist/types/autoresearch/index.d.ts +0 -2
- package/dist/types/autoresearch/state.d.ts +0 -17
- package/dist/types/autoresearch/storage.d.ts +0 -142
- package/dist/types/autoresearch/tools/init-experiment.d.ts +0 -31
- package/dist/types/autoresearch/tools/log-experiment.d.ts +0 -23
- package/dist/types/autoresearch/tools/run-experiment.d.ts +0 -8
- package/dist/types/autoresearch/tools/update-notes.d.ts +0 -12
- package/dist/types/commands/team.d.ts +0 -24
- package/dist/types/gjc-runtime/team-gc.d.ts +0 -7
- package/dist/types/gjc-runtime/team-launch.d.ts +0 -61
- package/dist/types/gjc-runtime/team-notify.d.ts +0 -45
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -336
- package/dist/types/gjc-runtime/team-store.d.ts +0 -168
- package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +0 -43
- package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +0 -92
- package/dist/types/gjc-runtime/team-workers.d.ts +0 -83
- package/dist/types/rlm/index.d.ts +0 -47
- package/dist/types/rlm/preset.d.ts +0 -23
- package/dist/types/rlm/python-tool.d.ts +0 -16
- package/src/autoresearch/command-resume.md +0 -14
- package/src/autoresearch/index.ts +0 -536
- package/src/autoresearch/prompt.md +0 -103
- package/src/autoresearch/resume-message.md +0 -10
- package/src/autoresearch/state.ts +0 -273
- package/src/autoresearch/storage.ts +0 -699
- package/src/autoresearch/tools/init-experiment.ts +0 -272
- package/src/autoresearch/tools/log-experiment.ts +0 -524
- package/src/autoresearch/tools/run-experiment.ts +0 -475
- package/src/autoresearch/tools/update-notes.ts +0 -109
- package/src/commands/rlm.ts +0 -19
- package/src/commands/team.ts +0 -238
- package/src/defaults/gjc/skills/deep-interview/auto-research-greenfield.md +0 -42
- package/src/defaults/gjc/skills/team/SKILL.md +0 -467
- package/src/gjc-runtime/team-gc.ts +0 -49
- package/src/gjc-runtime/team-launch.ts +0 -404
- package/src/gjc-runtime/team-notify.ts +0 -326
- package/src/gjc-runtime/team-runtime.ts +0 -5923
- package/src/gjc-runtime/team-store.ts +0 -878
- package/src/gjc-runtime/team-worker-heartbeat.ts +0 -149
- package/src/gjc-runtime/team-worker-memory-guard.ts +0 -363
- package/src/gjc-runtime/team-workers.ts +0 -1123
- package/src/prompts/system/rlm-report-command.md +0 -1
- package/src/prompts/system/rlm-research.md +0 -23
- package/src/rlm/index.ts +0 -365
- package/src/rlm/preset.ts +0 -76
- package/src/rlm/python-tool.ts +0 -68
package/src/crash/index-store.ts
CHANGED
|
@@ -78,6 +78,9 @@ export interface CrashSignatureEntry {
|
|
|
78
78
|
relayedAt?: number;
|
|
79
79
|
/** Occurrence record represented by the durable upstream watermark. */
|
|
80
80
|
relayedRecordId?: string;
|
|
81
|
+
/** Refusal marker for the exact record and sanitizer contract. */
|
|
82
|
+
relayRefusedRecordId?: string;
|
|
83
|
+
relayRefusedVersion?: string;
|
|
81
84
|
/** Issues this install already "+1"ed, so re-invocations cannot spam comments. */
|
|
82
85
|
commentedIssues?: string[];
|
|
83
86
|
}
|
|
@@ -151,6 +154,8 @@ const ENTRY_KEYS = new Set([
|
|
|
151
154
|
"acknowledgedAt",
|
|
152
155
|
"relayedAt",
|
|
153
156
|
"relayedRecordId",
|
|
157
|
+
"relayRefusedRecordId",
|
|
158
|
+
"relayRefusedVersion",
|
|
154
159
|
"commentedIssues",
|
|
155
160
|
]);
|
|
156
161
|
const INDEX_KEYS = new Set([
|
|
@@ -240,6 +245,12 @@ function parseEntry(value: unknown, now: number): CrashSignatureEntry | undefine
|
|
|
240
245
|
(typeof raw.lastAppendRecordId !== "string" || !/^[0-9a-f]{8,32}$/.test(raw.lastAppendRecordId))
|
|
241
246
|
)
|
|
242
247
|
return undefined;
|
|
248
|
+
if (
|
|
249
|
+
raw.relayRefusedRecordId !== undefined &&
|
|
250
|
+
(typeof raw.relayRefusedRecordId !== "string" || !/^[0-9a-f]{8,32}$/.test(raw.relayRefusedRecordId))
|
|
251
|
+
)
|
|
252
|
+
return undefined;
|
|
253
|
+
if (raw.relayRefusedVersion !== undefined && !isCleanString(raw.relayRefusedVersion, 64)) return undefined;
|
|
243
254
|
if (raw.reportedAt !== undefined && !isTimestamp(raw.reportedAt, now)) return undefined;
|
|
244
255
|
if (raw.acknowledgedAt !== undefined && !isTimestamp(raw.acknowledgedAt, now)) return undefined;
|
|
245
256
|
if (raw.relayedAt !== undefined && !isTimestamp(raw.relayedAt, now)) return undefined;
|
|
@@ -262,21 +273,49 @@ function parseEntry(value: unknown, now: number): CrashSignatureEntry | undefine
|
|
|
262
273
|
firstSeen: raw.firstSeen,
|
|
263
274
|
lastSeen: raw.lastSeen,
|
|
264
275
|
lastRecordId: raw.lastRecordId,
|
|
265
|
-
|
|
276
|
+
// Legacy entries do not carry append order. Keep it absent until a new
|
|
277
|
+
// journal occurrence supplies authoritative append-order evidence.
|
|
278
|
+
...(typeof raw.lastAppendRecordId === "string" ? { lastAppendRecordId: raw.lastAppendRecordId } : {}),
|
|
266
279
|
};
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
// Preserve idempotency and append-order watermarks before display metadata.
|
|
281
|
+
// A legacy entry may be exactly at the 1 KiB cap; dropping these fields would
|
|
282
|
+
// make a successful relay/comment look new on every subsequent invocation.
|
|
283
|
+
const optional: readonly [keyof CrashSignatureEntry, unknown][] = [
|
|
284
|
+
["relayedAt", raw.relayedAt],
|
|
285
|
+
["relayedRecordId", raw.relayedRecordId],
|
|
286
|
+
["relayRefusedRecordId", raw.relayRefusedRecordId],
|
|
287
|
+
["relayRefusedVersion", raw.relayRefusedVersion],
|
|
288
|
+
["commentedIssues", raw.commentedIssues === undefined ? undefined : [...(raw.commentedIssues as string[])]],
|
|
289
|
+
["reportedAt", raw.reportedAt],
|
|
290
|
+
["reportedIssueUrl", raw.reportedIssueUrl],
|
|
291
|
+
["acknowledgedAt", raw.acknowledgedAt],
|
|
292
|
+
];
|
|
293
|
+
for (const [key, value] of optional) {
|
|
294
|
+
if (value === undefined) continue;
|
|
295
|
+
if (key === "commentedIssues" && Array.isArray(value)) {
|
|
296
|
+
// Keep the newest idempotency markers that fit. Silently dropping the
|
|
297
|
+
// whole list would make a capped entry comment the same issue again.
|
|
298
|
+
for (let start = 0; start < value.length; start++) {
|
|
299
|
+
const candidate = { ...entry, [key]: value.slice(start) };
|
|
300
|
+
if (Buffer.byteLength(JSON.stringify(candidate), "utf8") <= CRASH_INDEX_ENTRY_MAX_BYTES) {
|
|
301
|
+
(entry as unknown as Record<string, unknown>)[key] = value.slice(start);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const candidate = { ...entry, [key]: value };
|
|
308
|
+
if (Buffer.byteLength(JSON.stringify(candidate), "utf8") <= CRASH_INDEX_ENTRY_MAX_BYTES)
|
|
309
|
+
(entry as unknown as Record<string, unknown>)[key] = value;
|
|
279
310
|
}
|
|
311
|
+
if (raw.relayedRecordId !== undefined && entry.relayedRecordId !== raw.relayedRecordId) return undefined;
|
|
312
|
+
if (raw.relayRefusedRecordId !== undefined && entry.relayRefusedRecordId !== raw.relayRefusedRecordId)
|
|
313
|
+
return undefined;
|
|
314
|
+
if (raw.relayRefusedVersion !== undefined && entry.relayRefusedVersion !== raw.relayRefusedVersion) return undefined;
|
|
315
|
+
// A legacy entry may be exactly at the cap. Display/report metadata is
|
|
316
|
+
// bounded normalization fodder, so it is safe to drop when upgrading it;
|
|
317
|
+
// refusing the whole entry would lose the occurrence and make a later
|
|
318
|
+
// journal merge quarantine an otherwise valid index.
|
|
280
319
|
if (Buffer.byteLength(JSON.stringify(entry), "utf8") > CRASH_INDEX_ENTRY_MAX_BYTES) return undefined;
|
|
281
320
|
return entry;
|
|
282
321
|
}
|
|
@@ -474,6 +513,51 @@ function evictOne(index: CrashIndex): boolean {
|
|
|
474
513
|
return true;
|
|
475
514
|
}
|
|
476
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Add a refusal watermark without allowing optional presentation/report data
|
|
518
|
+
* to make the entry unpersistable at the hard 1 KiB boundary. The marker is
|
|
519
|
+
* the durable safety property; older display metadata is deliberately the
|
|
520
|
+
* first thing sacrificed. Build the candidate first so callers never see a
|
|
521
|
+
* partially-mutated entry when even the mandatory fields cannot fit.
|
|
522
|
+
*/
|
|
523
|
+
function normalizeEntryForPersistence(candidate: CrashSignatureEntry): CrashSignatureEntry | undefined {
|
|
524
|
+
const fits = (value: CrashSignatureEntry): boolean =>
|
|
525
|
+
Buffer.byteLength(JSON.stringify(value), "utf8") <= CRASH_INDEX_ENTRY_MAX_BYTES;
|
|
526
|
+
if (fits(candidate)) return candidate;
|
|
527
|
+
if (candidate.commentedIssues !== undefined) {
|
|
528
|
+
for (let start = 0; start < candidate.commentedIssues.length; start++) {
|
|
529
|
+
const trimmed = { ...candidate, commentedIssues: candidate.commentedIssues.slice(start) };
|
|
530
|
+
if (fits(trimmed)) return trimmed;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Report/display metadata is advisory and can be reconstructed from the
|
|
535
|
+
// journal or re-entered by the user. Relay watermarks are intentionally not
|
|
536
|
+
// in this list: dropping one would make a successful/refused batch repeat.
|
|
537
|
+
for (const key of ["commentedIssues", "reportedIssueUrl", "reportedAt", "acknowledgedAt"] as const) {
|
|
538
|
+
delete candidate[key];
|
|
539
|
+
if (fits(candidate)) return candidate;
|
|
540
|
+
}
|
|
541
|
+
// Keep the error identity while shrinking only its human-facing preview.
|
|
542
|
+
if (candidate.messageClass) {
|
|
543
|
+
candidate.messageClass = boundMessageClass(candidate.messageClass.slice(0, 128));
|
|
544
|
+
if (fits(candidate)) return candidate;
|
|
545
|
+
}
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function withRefusalWatermark(
|
|
550
|
+
existing: CrashSignatureEntry,
|
|
551
|
+
recordId: string,
|
|
552
|
+
contractVersion: string,
|
|
553
|
+
): CrashSignatureEntry | undefined {
|
|
554
|
+
return normalizeEntryForPersistence({
|
|
555
|
+
...existing,
|
|
556
|
+
relayRefusedRecordId: recordId,
|
|
557
|
+
relayRefusedVersion: contractVersion,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
|
|
477
561
|
/** Apply one journal event to the in-memory index. Returns whether it changed anything. */
|
|
478
562
|
export function applyCrashEvent(index: CrashIndex, event: CrashEvent, now: number): boolean {
|
|
479
563
|
if (event.at > now + MAX_FUTURE_SKEW_MS || event.at < MIN_TIMESTAMP_MS) return false;
|
|
@@ -511,6 +595,16 @@ export function applyCrashEvent(index: CrashIndex, event: CrashEvent, now: numbe
|
|
|
511
595
|
}
|
|
512
596
|
return true;
|
|
513
597
|
}
|
|
598
|
+
if (event.kind === "refused") {
|
|
599
|
+
if (!existing || existing.fpv !== event.fpv) return false;
|
|
600
|
+
if (existing.relayRefusedRecordId === event.recordId && existing.relayRefusedVersion === event.contractVersion)
|
|
601
|
+
return false;
|
|
602
|
+
const updated = withRefusalWatermark(existing, event.recordId, event.contractVersion);
|
|
603
|
+
if (!updated) return false;
|
|
604
|
+
for (const key of Object.keys(existing)) delete (existing as unknown as Record<string, unknown>)[key];
|
|
605
|
+
Object.assign(existing, updated);
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
514
608
|
if (event.kind === "acknowledged") {
|
|
515
609
|
if (!existing) return false;
|
|
516
610
|
if (existing.acknowledgedAt !== undefined && existing.acknowledgedAt >= event.at) return false;
|
|
@@ -527,21 +621,28 @@ export function applyCrashEvent(index: CrashIndex, event: CrashEvent, now: numbe
|
|
|
527
621
|
return false;
|
|
528
622
|
}
|
|
529
623
|
if (index.recentEventIds.includes(event.recordId)) return false;
|
|
530
|
-
index.recentEventIds.push(event.recordId);
|
|
531
|
-
if (index.recentEventIds.length > RECENT_EVENT_ID_LIMIT)
|
|
532
|
-
index.recentEventIds.splice(0, index.recentEventIds.length - RECENT_EVENT_ID_LIMIT);
|
|
533
624
|
// A journal occurrence is newer authority than a prior eviction tombstone.
|
|
534
625
|
// Clear it before capacity eviction so a full retirement ledger cannot block
|
|
535
626
|
// the signature's explicit revival.
|
|
536
627
|
if (existing) {
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
628
|
+
const updated: CrashSignatureEntry = {
|
|
629
|
+
...existing,
|
|
630
|
+
lifetimeCount: existing.lifetimeCount + 1,
|
|
631
|
+
firstSeen: Math.min(existing.firstSeen, event.at),
|
|
632
|
+
lastAppendRecordId: event.recordId,
|
|
633
|
+
};
|
|
540
634
|
if (event.at >= existing.lastSeen) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (event.messageClass)
|
|
635
|
+
updated.lastSeen = event.at;
|
|
636
|
+
updated.lastRecordId = event.recordId;
|
|
637
|
+
if (event.messageClass) updated.messageClass = boundMessageClass(event.messageClass);
|
|
544
638
|
}
|
|
639
|
+
const normalized = normalizeEntryForPersistence(updated);
|
|
640
|
+
if (!normalized) return false;
|
|
641
|
+
for (const key of Object.keys(existing)) delete (existing as unknown as Record<string, unknown>)[key];
|
|
642
|
+
Object.assign(existing, normalized);
|
|
643
|
+
index.recentEventIds.push(event.recordId);
|
|
644
|
+
if (index.recentEventIds.length > RECENT_EVENT_ID_LIMIT)
|
|
645
|
+
index.recentEventIds.splice(0, index.recentEventIds.length - RECENT_EVENT_ID_LIMIT);
|
|
545
646
|
return true;
|
|
546
647
|
}
|
|
547
648
|
if (Object.keys(index.signatures).length >= CRASH_INDEX_MAX_SIGNATURES && !evictOne(index)) {
|
|
@@ -561,6 +662,9 @@ export function applyCrashEvent(index: CrashIndex, event: CrashEvent, now: numbe
|
|
|
561
662
|
lastRecordId: event.recordId,
|
|
562
663
|
lastAppendRecordId: event.recordId,
|
|
563
664
|
};
|
|
665
|
+
index.recentEventIds.push(event.recordId);
|
|
666
|
+
if (index.recentEventIds.length > RECENT_EVENT_ID_LIMIT)
|
|
667
|
+
index.recentEventIds.splice(0, index.recentEventIds.length - RECENT_EVENT_ID_LIMIT);
|
|
564
668
|
return true;
|
|
565
669
|
}
|
|
566
670
|
|
|
@@ -147,3 +147,10 @@ export function findLatestRecord(contents: string, fingerprint: string): LoadedC
|
|
|
147
147
|
const matches = parseCrashRecords(contents).filter(record => record.fingerprint === fingerprint);
|
|
148
148
|
return matches.at(-1);
|
|
149
149
|
}
|
|
150
|
+
|
|
151
|
+
/** Identity-bearing record bound to the indexed append-order identity. */
|
|
152
|
+
export function findRecordById(contents: string, fingerprint: string, recordId: string): LoadedCrashRecord | undefined {
|
|
153
|
+
return parseRecoverableCrashRecords(contents).find(
|
|
154
|
+
record => record.fingerprint === fingerprint && record.recordId === recordId,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -22,18 +22,7 @@
|
|
|
22
22
|
import { createHash } from "node:crypto";
|
|
23
23
|
import * as fs from "node:fs/promises";
|
|
24
24
|
import * as path from "node:path";
|
|
25
|
-
import {
|
|
26
|
-
getCrashEventsPath,
|
|
27
|
-
getCrashIndexPath,
|
|
28
|
-
getCrashLogPath,
|
|
29
|
-
getHandledErrorEventsPath,
|
|
30
|
-
getHandledErrorIndexPath,
|
|
31
|
-
getHandledErrorLogPath,
|
|
32
|
-
getTrustedAgentFile,
|
|
33
|
-
isProjectEnvDeclaration,
|
|
34
|
-
normalizeCrashFrames,
|
|
35
|
-
VERSION,
|
|
36
|
-
} from "@gajae-code/utils";
|
|
25
|
+
import { getTrustedAgentFile, normalizeCrashFrames, VERSION } from "@gajae-code/utils";
|
|
37
26
|
import { $credentialEnv } from "@gajae-code/utils/env";
|
|
38
27
|
import {
|
|
39
28
|
type CrashSignatureView,
|
|
@@ -42,10 +31,13 @@ import {
|
|
|
42
31
|
listCrashSignatures,
|
|
43
32
|
recordCrashStateEvent,
|
|
44
33
|
} from "../index-store";
|
|
45
|
-
import {
|
|
34
|
+
import { findRecordById } from "../record-loader";
|
|
46
35
|
import { parseSentryDsn, type SentryDsn } from "./dsn";
|
|
47
36
|
import { buildCrashEnvelope, type CrashEventFrame, sentryAuthHeader } from "./envelope";
|
|
48
37
|
|
|
38
|
+
/** Version of the complete sanitizer/egress contract persisted in refusals. */
|
|
39
|
+
export const SANITIZER_EGRESS_CONTRACT_VERSION = "sanitize-external-crash-v1";
|
|
40
|
+
|
|
49
41
|
/** Trusted environment variable form of the DSN, for CI and one-off runs. */
|
|
50
42
|
export const CRASH_UPSTREAM_DSN_ENV = "GJC_CRASH_SENTRY_DSN";
|
|
51
43
|
|
|
@@ -56,6 +48,25 @@ export const CRASH_UPSTREAM_DSN_ENV = "GJC_CRASH_SENTRY_DSN";
|
|
|
56
48
|
const MAX_RELAY_PER_RUN = 8;
|
|
57
49
|
const RELAY_TIMEOUT_MS = 10_000;
|
|
58
50
|
const RELAY_CLAIM_TTL_MS = RELAY_TIMEOUT_MS * 2;
|
|
51
|
+
const CRASH_LOG_READ_MAX_BYTES = 1024 * 1024;
|
|
52
|
+
const NOFOLLOW = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
53
|
+
|
|
54
|
+
async function readCrashLogNoFollow(filePath: string): Promise<string | undefined> {
|
|
55
|
+
let handle: fs.FileHandle | undefined;
|
|
56
|
+
try {
|
|
57
|
+
handle = await fs.open(filePath, fs.constants.O_RDONLY | NOFOLLOW);
|
|
58
|
+
const stat = await handle.stat();
|
|
59
|
+
if (!stat.isFile()) return undefined;
|
|
60
|
+
const length = Math.min(stat.size, CRASH_LOG_READ_MAX_BYTES);
|
|
61
|
+
const buffer = Buffer.allocUnsafe(length);
|
|
62
|
+
await handle.read(buffer, 0, length, Math.max(0, stat.size - length));
|
|
63
|
+
return buffer.toString("utf8");
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined;
|
|
66
|
+
} finally {
|
|
67
|
+
await handle?.close().catch(() => {});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
59
70
|
|
|
60
71
|
export interface CrashRelayConfig {
|
|
61
72
|
readonly upstream: "off" | "sentry";
|
|
@@ -170,6 +181,18 @@ export function resolveRelayDsn(
|
|
|
170
181
|
*/
|
|
171
182
|
export function isRelayDue(signature: CrashSignatureView): boolean {
|
|
172
183
|
const appendId = signature.lastAppendRecordId ?? signature.lastRecordId;
|
|
184
|
+
if (
|
|
185
|
+
signature.relayRefusedRecordId === appendId &&
|
|
186
|
+
signature.relayRefusedVersion === SANITIZER_EGRESS_CONTRACT_VERSION
|
|
187
|
+
)
|
|
188
|
+
return false;
|
|
189
|
+
// Modern entries with a durable relay record id use exact identity. Legacy
|
|
190
|
+
// entries upgraded from a wall-clock-only relay have no relayedRecordId, so
|
|
191
|
+
// retain their timestamp coverage compatibility until a new append exists.
|
|
192
|
+
if (signature.lastAppendRecordId !== undefined) {
|
|
193
|
+
if (signature.relayedRecordId !== undefined) return signature.relayedRecordId !== appendId;
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
173
196
|
if (signature.relayedRecordId !== undefined) {
|
|
174
197
|
if (signature.relayedRecordId === appendId) return false;
|
|
175
198
|
if (
|
|
@@ -195,26 +218,51 @@ function relayEventId(signature: CrashSignatureView): string {
|
|
|
195
218
|
.slice(0, 32);
|
|
196
219
|
}
|
|
197
220
|
|
|
221
|
+
async function persistRefusal(
|
|
222
|
+
paths: CrashStatePaths,
|
|
223
|
+
signature: CrashSignatureView,
|
|
224
|
+
now: () => number,
|
|
225
|
+
): Promise<boolean> {
|
|
226
|
+
try {
|
|
227
|
+
const index = await recordCrashStateEvent(
|
|
228
|
+
{
|
|
229
|
+
kind: "refused",
|
|
230
|
+
fingerprint: signature.fingerprint,
|
|
231
|
+
fpv: signature.fpv,
|
|
232
|
+
recordId: relayAppendRecordId(signature),
|
|
233
|
+
contractVersion: SANITIZER_EGRESS_CONTRACT_VERSION,
|
|
234
|
+
at: signature.lastSeen,
|
|
235
|
+
},
|
|
236
|
+
{ paths, now: now() },
|
|
237
|
+
);
|
|
238
|
+
const entry = index.signatures[signature.fingerprint];
|
|
239
|
+
return (
|
|
240
|
+
entry?.relayRefusedRecordId === relayAppendRecordId(signature) &&
|
|
241
|
+
entry.relayRefusedVersion === SANITIZER_EGRESS_CONTRACT_VERSION
|
|
242
|
+
);
|
|
243
|
+
} catch {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
198
248
|
/**
|
|
199
|
-
* Automatic relay
|
|
200
|
-
*
|
|
201
|
-
*
|
|
249
|
+
* Automatic relay always reads the trusted agent stores. XDG-aware paths remain
|
|
250
|
+
* available to ordinary crash state operations, but never select automatic-egress
|
|
251
|
+
* input files.
|
|
202
252
|
*/
|
|
203
253
|
export function resolveTrustedRelayStatePaths(): CrashStatePaths {
|
|
204
|
-
const useTrustedAgent = isProjectEnvDeclaration("XDG_STATE_HOME");
|
|
205
254
|
return {
|
|
206
|
-
index:
|
|
207
|
-
events:
|
|
208
|
-
crashLog:
|
|
255
|
+
index: getTrustedAgentFile("gjc-crash-index.json"),
|
|
256
|
+
events: getTrustedAgentFile("gjc-crash-events.jsonl"),
|
|
257
|
+
crashLog: getTrustedAgentFile("gjc-crash.log"),
|
|
209
258
|
};
|
|
210
259
|
}
|
|
211
260
|
|
|
212
261
|
export function resolveTrustedHandledRelayStatePaths(): CrashStatePaths {
|
|
213
|
-
const useTrustedAgent = isProjectEnvDeclaration("XDG_STATE_HOME");
|
|
214
262
|
return {
|
|
215
|
-
index:
|
|
216
|
-
events:
|
|
217
|
-
crashLog:
|
|
263
|
+
index: getTrustedAgentFile("gjc-error-index.json"),
|
|
264
|
+
events: getTrustedAgentFile("gjc-error-events.jsonl"),
|
|
265
|
+
crashLog: getTrustedAgentFile("gjc-error.log"),
|
|
218
266
|
};
|
|
219
267
|
}
|
|
220
268
|
|
|
@@ -292,10 +340,8 @@ export async function relayCrashSignatures(options: CrashRelayOptions): Promise<
|
|
|
292
340
|
.slice(0, limit);
|
|
293
341
|
if (signatures.length === 0) return { status: "skipped", reason: "nothing-to-relay" };
|
|
294
342
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
crashLog = await fs.readFile(paths.crashLog, "utf8");
|
|
298
|
-
} catch {
|
|
343
|
+
const crashLog = await readCrashLogNoFollow(paths.crashLog);
|
|
344
|
+
if (crashLog === undefined) {
|
|
299
345
|
// A missing or unreadable log means no stack to attach; the signature
|
|
300
346
|
// metadata alone is not worth a send.
|
|
301
347
|
return { status: "skipped", reason: "nothing-to-relay" };
|
|
@@ -314,9 +360,10 @@ export async function relayCrashSignatures(options: CrashRelayOptions): Promise<
|
|
|
314
360
|
if (claim.status === "failed") failed++;
|
|
315
361
|
continue;
|
|
316
362
|
}
|
|
317
|
-
const record =
|
|
318
|
-
if (!record) {
|
|
319
|
-
refused++;
|
|
363
|
+
const record = findRecordById(crashLog, signature.fingerprint, relayAppendRecordId(signature));
|
|
364
|
+
if (!record || record.fpv !== signature.fpv) {
|
|
365
|
+
if (await persistRefusal(paths, signature, now)) refused++;
|
|
366
|
+
else failed++;
|
|
320
367
|
await claim.release().catch(() => {});
|
|
321
368
|
continue;
|
|
322
369
|
}
|
|
@@ -324,8 +371,8 @@ export async function relayCrashSignatures(options: CrashRelayOptions): Promise<
|
|
|
324
371
|
const envelope = buildCrashEnvelope({
|
|
325
372
|
eventId,
|
|
326
373
|
fingerprint: signature.fingerprint,
|
|
327
|
-
errorName:
|
|
328
|
-
messageClass:
|
|
374
|
+
errorName: record.errorName,
|
|
375
|
+
messageClass: record.messageClass,
|
|
329
376
|
frames: toEventFrames(record.body),
|
|
330
377
|
firstSeen: signature.firstSeen,
|
|
331
378
|
lastSeen: signature.lastSeen,
|
|
@@ -338,7 +385,8 @@ export async function relayCrashSignatures(options: CrashRelayOptions): Promise<
|
|
|
338
385
|
});
|
|
339
386
|
if (!envelope.ok) {
|
|
340
387
|
// Fail closed. Deliberately no retry with a reduced payload.
|
|
341
|
-
refused++;
|
|
388
|
+
if (await persistRefusal(paths, signature, now)) refused++;
|
|
389
|
+
else failed++;
|
|
342
390
|
await claim.release().catch(() => {});
|
|
343
391
|
continue;
|
|
344
392
|
}
|
package/src/cursor.ts
CHANGED
|
@@ -14,6 +14,14 @@ import type {
|
|
|
14
14
|
CursorExecHandlers as ICursorExecHandlers,
|
|
15
15
|
ToolResultMessage,
|
|
16
16
|
} from "@gajae-code/ai/core";
|
|
17
|
+
import {
|
|
18
|
+
piEscapeRegexLiteral,
|
|
19
|
+
piJoinPath,
|
|
20
|
+
piLimit,
|
|
21
|
+
piLsPath,
|
|
22
|
+
piReadPath,
|
|
23
|
+
piTimeout,
|
|
24
|
+
} from "@gajae-code/ai/providers/cursor/exec-modern";
|
|
17
25
|
import { sanitizeText } from "@gajae-code/utils";
|
|
18
26
|
import { resolveToCwd } from "./tools/path-utils";
|
|
19
27
|
|
|
@@ -32,6 +40,8 @@ type CursorExecEventEmitter = (event: AgentEvent, dispatchedTool?: AgentTool) =>
|
|
|
32
40
|
interface CursorExecBridgeOptions {
|
|
33
41
|
cwd: string;
|
|
34
42
|
tools: Map<string, AgentTool>;
|
|
43
|
+
getEditReplaceTool?: () => AgentTool | undefined;
|
|
44
|
+
createSearchTool?: (options: { context?: number; totalMatchLimit?: number }) => AgentTool | undefined;
|
|
35
45
|
getToolContext?: () => AgentToolContext | undefined;
|
|
36
46
|
emitEvent?: CursorExecEventEmitter;
|
|
37
47
|
createEventEmitter?: () => ((event: AgentEvent) => void) | undefined;
|
|
@@ -66,8 +76,9 @@ async function executeTool(
|
|
|
66
76
|
toolName: string,
|
|
67
77
|
toolCallId: string,
|
|
68
78
|
args: Record<string, unknown>,
|
|
79
|
+
overrideTool?: AgentTool,
|
|
69
80
|
): Promise<ToolResultMessage> {
|
|
70
|
-
const tool = options.tools.get(toolName);
|
|
81
|
+
const tool = overrideTool ?? options.tools.get(toolName);
|
|
71
82
|
if (!tool) {
|
|
72
83
|
const result = buildToolErrorResult(`Tool "${toolName}" not available`);
|
|
73
84
|
return createToolResultMessage(toolCallId, toolName, result, true);
|
|
@@ -109,6 +120,7 @@ async function executeTool(
|
|
|
109
120
|
result = buildToolErrorResult(message);
|
|
110
121
|
isError = true;
|
|
111
122
|
}
|
|
123
|
+
isError ||= result.isError === true;
|
|
112
124
|
|
|
113
125
|
const sanitizedFinalResult: AgentToolResult<unknown> = {
|
|
114
126
|
content: result.content.map(c => (c.type === "text" ? { ...c, text: sanitizeText(c.text) } : c)),
|
|
@@ -203,6 +215,13 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
203
215
|
this.shellStream = this.shellStream.bind(this);
|
|
204
216
|
this.diagnostics = this.diagnostics.bind(this);
|
|
205
217
|
this.mcp = this.mcp.bind(this);
|
|
218
|
+
this.piRead = this.piRead.bind(this);
|
|
219
|
+
this.piBash = this.piBash.bind(this);
|
|
220
|
+
this.piEdit = this.piEdit.bind(this);
|
|
221
|
+
this.piWrite = this.piWrite.bind(this);
|
|
222
|
+
this.piGrep = this.piGrep.bind(this);
|
|
223
|
+
this.piFind = this.piFind.bind(this);
|
|
224
|
+
this.piLs = this.piLs.bind(this);
|
|
206
225
|
}
|
|
207
226
|
|
|
208
227
|
#optionsForCall(): CursorExecBridgeOptions {
|
|
@@ -390,6 +409,70 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
390
409
|
return createToolResultMessage(toolCallId, toolName, result, isError);
|
|
391
410
|
}
|
|
392
411
|
|
|
412
|
+
async piRead(call: Parameters<NonNullable<ICursorExecHandlers["piRead"]>>[0]) {
|
|
413
|
+
const composed = piReadPath(call.args.path, call.args.offset, call.args.limit);
|
|
414
|
+
if (composed === null) {
|
|
415
|
+
return createToolResultMessage(call.toolCallId, "read", { content: [{ type: "text", text: "" }] }, false);
|
|
416
|
+
}
|
|
417
|
+
return executeTool(this.#optionsForCall(), "read", call.toolCallId, { path: composed });
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async piBash(call: Parameters<NonNullable<ICursorExecHandlers["piBash"]>>[0]) {
|
|
421
|
+
return executeTool(this.#optionsForCall(), "bash", call.toolCallId, {
|
|
422
|
+
command: call.args.command,
|
|
423
|
+
timeout: piTimeout(call.args.timeout),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async piEdit(call: Parameters<NonNullable<ICursorExecHandlers["piEdit"]>>[0]) {
|
|
428
|
+
const edits = call.args.edits.map(edit => ({
|
|
429
|
+
old_text: edit.oldText,
|
|
430
|
+
new_text: edit.newText,
|
|
431
|
+
}));
|
|
432
|
+
return executeTool(
|
|
433
|
+
this.#optionsForCall(),
|
|
434
|
+
"edit",
|
|
435
|
+
call.toolCallId,
|
|
436
|
+
{ path: call.args.path, edits },
|
|
437
|
+
this.options.getEditReplaceTool?.(),
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async piWrite(call: Parameters<NonNullable<ICursorExecHandlers["piWrite"]>>[0]) {
|
|
442
|
+
return executeTool(this.#optionsForCall(), "write", call.toolCallId, {
|
|
443
|
+
path: call.args.path,
|
|
444
|
+
content: call.args.content,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async piGrep(call: Parameters<NonNullable<ICursorExecHandlers["piGrep"]>>[0]) {
|
|
449
|
+
const { pattern, path, glob, ignoreCase, literal, context, limit } = call.args;
|
|
450
|
+
const perCallSearch = this.options.createSearchTool?.({
|
|
451
|
+
context: context === undefined ? undefined : Math.max(0, Math.floor(context)),
|
|
452
|
+
totalMatchLimit: piLimit(limit),
|
|
453
|
+
});
|
|
454
|
+
const args: Record<string, unknown> = {
|
|
455
|
+
pattern: literal === true ? piEscapeRegexLiteral(pattern) : pattern,
|
|
456
|
+
paths: [glob ? piJoinPath(path, glob) : path || "."],
|
|
457
|
+
};
|
|
458
|
+
if (ignoreCase === true) args.i = true;
|
|
459
|
+
return executeTool(this.#optionsForCall(), "search", call.toolCallId, args, perCallSearch);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async piFind(call: Parameters<NonNullable<ICursorExecHandlers["piFind"]>>[0]) {
|
|
463
|
+
const { pattern, path, limit } = call.args;
|
|
464
|
+
return executeTool(this.#optionsForCall(), "find", call.toolCallId, {
|
|
465
|
+
paths: [piJoinPath(path, pattern)],
|
|
466
|
+
limit: piLimit(limit),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
async piLs(call: Parameters<NonNullable<ICursorExecHandlers["piLs"]>>[0]) {
|
|
471
|
+
return executeTool(this.#optionsForCall(), "read", call.toolCallId, {
|
|
472
|
+
path: piLsPath(call.args.path),
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
393
476
|
async diagnostics(args: Parameters<NonNullable<ICursorExecHandlers["diagnostics"]>>[0]) {
|
|
394
477
|
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
395
478
|
const toolResultMessage = await executeTool(this.#optionsForCall(), "lsp", toolCallId, {
|
package/src/debug/index.ts
CHANGED
|
@@ -327,9 +327,27 @@ export class DebugSelectorComponent extends Container {
|
|
|
327
327
|
}
|
|
328
328
|
|
|
329
329
|
async #handleViewLogs(): Promise<void> {
|
|
330
|
+
// The selector restores the reusable composer synchronously before this
|
|
331
|
+
// async operation starts. Capture both the exact mounted child and the
|
|
332
|
+
// container revision so a stale completion cannot mistake a newer
|
|
333
|
+
// non-Container owner (RawSSE/Input) for the composer, or reclaim the
|
|
334
|
+
// same composer after a newer overlay has already opened and closed.
|
|
335
|
+
const expectedComposerMount =
|
|
336
|
+
this.ctx.editorContainer.children.length === 1 ? this.ctx.editorContainer.children[0] : undefined;
|
|
337
|
+
const expectedMountRevision = this.ctx.editorContainer.getRenderRevision();
|
|
330
338
|
try {
|
|
331
339
|
const logSource = await createDebugLogSource();
|
|
332
340
|
const logs = await logSource.getInitialText();
|
|
341
|
+
// The awaits above leave a window where a newer overlay can take over
|
|
342
|
+
// the editor container (or the UI can stop). Mount only when the exact
|
|
343
|
+
// child and the mount revision captured before the awaits still match.
|
|
344
|
+
if (
|
|
345
|
+
expectedComposerMount === undefined ||
|
|
346
|
+
this.ctx.editorContainer.getRenderRevision() !== expectedMountRevision ||
|
|
347
|
+
this.ctx.editorContainer.children.length !== 1 ||
|
|
348
|
+
this.ctx.editorContainer.children[0] !== expectedComposerMount
|
|
349
|
+
)
|
|
350
|
+
return;
|
|
333
351
|
if (!logs && !logSource.hasOlderLogs()) {
|
|
334
352
|
this.ctx.showWarning("No log entries found for today.");
|
|
335
353
|
return;
|
|
@@ -345,6 +363,10 @@ export class DebugSelectorComponent extends Container {
|
|
|
345
363
|
logSource,
|
|
346
364
|
});
|
|
347
365
|
|
|
366
|
+
// The composer is reusable across overlays; detach it before clearing
|
|
367
|
+
// so clear() disposes only the transient viewer, not the editor's
|
|
368
|
+
// tab-width listener (disposal is terminal).
|
|
369
|
+
this.ctx.editorContainer.detachChild(this.ctx.editor);
|
|
348
370
|
this.ctx.editorContainer.clear();
|
|
349
371
|
this.ctx.editorContainer.addChild(viewer);
|
|
350
372
|
this.ctx.ui.setFocus(viewer);
|
|
@@ -364,6 +386,10 @@ export class DebugSelectorComponent extends Container {
|
|
|
364
386
|
onUpdate: () => this.ctx.ui.requestRender(),
|
|
365
387
|
});
|
|
366
388
|
|
|
389
|
+
// The composer is reusable across overlays; detach it before clearing
|
|
390
|
+
// so clear() disposes only the transient viewer, not the editor's
|
|
391
|
+
// tab-width listener (disposal is terminal).
|
|
392
|
+
this.ctx.editorContainer.detachChild(this.ctx.editor);
|
|
367
393
|
this.ctx.editorContainer.clear();
|
|
368
394
|
this.ctx.editorContainer.addChild(viewer);
|
|
369
395
|
this.ctx.ui.setFocus(viewer);
|