@gajae-code/coding-agent 0.18.12 → 0.19.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 +467 -0
- package/dist/types/capability/rule.d.ts +7 -2
- package/dist/types/cli/append-system-prompt.d.ts +6 -0
- package/dist/types/cli/args.d.ts +7 -2
- package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
- package/dist/types/cli/customize-doctor.d.ts +4 -3
- package/dist/types/cli/public-command-errors.d.ts +1 -0
- package/dist/types/cli/root-flags.d.ts +0 -12
- package/dist/types/cli/update-cli.d.ts +7 -2
- package/dist/types/cli-main.d.ts +0 -6
- package/dist/types/commands/launch.d.ts +0 -6
- package/dist/types/commands/migrate.d.ts +1 -1
- package/dist/types/commands/sdk.d.ts +8 -0
- package/dist/types/commit/map-reduce/index.d.ts +4 -4
- package/dist/types/config/autorouting-tier-map.d.ts +23 -1
- package/dist/types/config/config-hot-reload.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +44 -2
- package/dist/types/config/model-profile-activation.d.ts +19 -5
- package/dist/types/config/model-registry.d.ts +16 -3
- package/dist/types/config/model-resolver.d.ts +5 -4
- package/dist/types/config/settings-schema.d.ts +11 -1
- package/dist/types/coordinator-mcp/server.d.ts +4 -0
- package/dist/types/customization/import.d.ts +2 -0
- package/dist/types/customization/types.d.ts +1 -1
- package/dist/types/dap/session.d.ts +11 -2
- package/dist/types/discovery/helpers.d.ts +7 -2
- package/dist/types/edit/index.d.ts +5 -0
- package/dist/types/edit/modes/patch.d.ts +9 -0
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/export/ttsr.d.ts +19 -0
- package/dist/types/extensibility/extensions/types.d.ts +5 -1
- package/dist/types/extensibility/slash-commands.d.ts +1 -0
- package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
- package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
- package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
- package/dist/types/hindsight/content.d.ts +5 -6
- package/dist/types/internal-urls/types.d.ts +2 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
- package/dist/types/main.d.ts +23 -18
- package/dist/types/migrate/action-planner.d.ts +10 -1
- package/dist/types/migrate/adapters/codex.d.ts +1 -1
- package/dist/types/migrate/adapters/index.d.ts +35 -3
- package/dist/types/migrate/hook-module.d.ts +23 -0
- package/dist/types/migrate/report.d.ts +2 -1
- package/dist/types/migrate/types.d.ts +69 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +43 -0
- package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +15 -11
- package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
- package/dist/types/sdk/broker/broker-exit.d.ts +1 -1
- package/dist/types/sdk/broker/broker.d.ts +22 -1
- package/dist/types/sdk/broker/ensure.d.ts +5 -2
- package/dist/types/sdk/broker/hop.d.ts +9 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
- package/dist/types/sdk/broker/session-index.d.ts +2 -0
- package/dist/types/sdk/broker/transport.d.ts +2 -0
- package/dist/types/sdk/bus/index.d.ts +21 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +7 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
- package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
- package/dist/types/sdk/host/control/operations.d.ts +5 -1
- package/dist/types/sdk/host/events.d.ts +4 -0
- package/dist/types/sdk/host/host.d.ts +2 -1
- package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
- package/dist/types/sdk/host/session-runtime.d.ts +8 -8
- package/dist/types/sdk/host/surface-policy.d.ts +2 -1
- package/dist/types/sdk/lifecycle/service.d.ts +12 -0
- package/dist/types/sdk/prompt-status.d.ts +11 -4
- package/dist/types/session/agent-session.d.ts +50 -8
- package/dist/types/session/session-manager.d.ts +16 -0
- package/dist/types/skill-state/workflow-mutation-guard.d.ts +13 -1
- package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
- package/dist/types/ssh/ssh-executor.d.ts +8 -1
- package/dist/types/system-prompt.d.ts +3 -2
- package/dist/types/task/role-body.d.ts +11 -0
- package/dist/types/tools/awaiting-input.d.ts +28 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +6 -0
- package/dist/types/tools/bash-skill-urls.d.ts +4 -0
- package/dist/types/tools/bash.d.ts +7 -0
- package/dist/types/tools/descriptors.d.ts +13 -2
- package/dist/types/tools/fetch.d.ts +6 -0
- package/dist/types/tools/irc.d.ts +5 -3
- package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/utils/file-mentions.d.ts +5 -0
- package/dist/types/utils/image-limits.d.ts +7 -0
- package/dist/types/utils/image-loading.d.ts +0 -1
- package/dist/types/utils/open.d.ts +0 -1
- package/dist/types/utils/pasted-image-loading.d.ts +1 -5
- package/dist/types/utils/prompt-suggestion.d.ts +2 -2
- package/dist/types/utils/shell-snapshot.d.ts +2 -0
- package/dist/types/utils/title-generator.d.ts +13 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -7
- package/package.json +9 -8
- package/scripts/acp-conformance-agent.ts +12 -2
- package/scripts/generate-sdk-operation-inventory.ts +16 -0
- package/src/capability/fs.ts +31 -3
- package/src/capability/rule.ts +11 -2
- package/src/cli/append-system-prompt.ts +12 -0
- package/src/cli/args.ts +11 -7
- package/src/cli/customize-doctor-precedence.ts +31 -0
- package/src/cli/customize-doctor.ts +49 -39
- package/src/cli/fast-help.ts +0 -2
- package/src/cli/migrate-cli.ts +14 -3
- package/src/cli/public-command-errors.ts +2 -0
- package/src/cli/root-flags.ts +0 -2
- package/src/cli/update-cli.ts +247 -6
- package/src/commands/harness.ts +25 -15
- package/src/commands/migrate.ts +3 -3
- package/src/commands/sdk.ts +81 -11
- package/src/commit/agentic/agent.ts +6 -6
- package/src/commit/analysis/conventional.ts +14 -5
- package/src/commit/map-reduce/index.ts +7 -7
- package/src/commit/pipeline.ts +12 -12
- package/src/config/autorouting-tier-map.ts +20 -1
- package/src/config/config-file.ts +106 -3
- package/src/config/config-hot-reload.ts +8 -0
- package/src/config/file-lock.ts +1431 -304
- package/src/config/keybindings.ts +154 -7
- package/src/config/model-profile-activation.ts +177 -39
- package/src/config/model-registry.ts +380 -55
- package/src/config/model-resolver.ts +49 -21
- package/src/config/settings-schema.ts +13 -1
- package/src/config/settings.ts +2 -0
- package/src/coordinator-mcp/server.ts +37 -2
- package/src/crash/sanitize.ts +3 -1
- package/src/customization/import.ts +276 -7
- package/src/customization/inventory.ts +24 -8
- package/src/customization/types.ts +1 -0
- package/src/dap/client.ts +3 -2
- package/src/dap/session.ts +161 -36
- package/src/debug/crash-diagnostics.ts +87 -5
- package/src/debug/report-bundle.ts +71 -2
- package/src/discovery/builtin.ts +4 -3
- package/src/discovery/helpers.ts +24 -14
- package/src/edit/index.ts +191 -5
- package/src/edit/modes/patch.ts +27 -0
- package/src/eval/py/runtime.ts +44 -4
- package/src/exec/non-interactive-env.ts +0 -1
- package/src/export/html/index.ts +60 -20
- package/src/export/ttsr.ts +176 -1
- package/src/extensibility/extensions/loader.ts +2 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
- package/src/extensibility/extensions/types.ts +5 -1
- package/src/extensibility/hooks/loader.ts +10 -2
- package/src/extensibility/plugins/marketplace/manager.ts +36 -10
- package/src/extensibility/slash-commands.ts +5 -0
- package/src/gjc-runtime/gc-runtime.ts +4 -1
- package/src/gjc-runtime/launch-tmux.ts +23 -13
- package/src/gjc-runtime/ralplan-current-final.ts +294 -0
- package/src/gjc-runtime/ralplan-runtime.ts +95 -1
- package/src/gjc-runtime/state-writer.ts +109 -5
- package/src/gjc-runtime/tmux-sessions.ts +93 -13
- package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
- package/src/gjc-runtime/ultragoal-guard.ts +9 -1
- package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
- package/src/harness-control-plane/finalize.ts +11 -0
- package/src/harness-control-plane/receipt-spool.ts +105 -28
- package/src/harness-control-plane/storage.ts +124 -6
- package/src/hindsight/content.ts +116 -16
- package/src/hindsight/mental-models.ts +22 -10
- package/src/hindsight/state.ts +7 -4
- package/src/hooks/native-skill-hook.ts +3 -1
- package/src/internal-urls/artifact-protocol.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +14 -14
- package/src/internal-urls/local-protocol.ts +28 -3
- package/src/internal-urls/types.ts +2 -0
- package/src/lsp/client.ts +2 -1
- package/src/lsp/clients/biome-client.ts +2 -0
- package/src/lsp/clients/swiftlint-client.ts +2 -0
- package/src/main.ts +330 -92
- package/src/memories/index.ts +25 -2
- package/src/migrate/action-planner.ts +517 -2
- package/src/migrate/adapters/claude-code.ts +141 -3
- package/src/migrate/adapters/codex.ts +57 -5
- package/src/migrate/adapters/index.ts +240 -5
- package/src/migrate/adapters/opencode.ts +4 -0
- package/src/migrate/executor.ts +141 -3
- package/src/migrate/hook-module.ts +193 -0
- package/src/migrate/report.ts +45 -1
- package/src/migrate/types.ts +73 -3
- package/src/modes/acp/acp-agent.ts +10 -1
- package/src/modes/components/agent-dashboard.ts +20 -4
- package/src/modes/components/customization/customization-dashboard.ts +7 -0
- package/src/modes/components/customization/import-wizard.ts +29 -3
- package/src/modes/components/model-selector.ts +157 -103
- package/src/modes/components/welcome.ts +3 -1
- package/src/modes/controllers/command-controller.ts +271 -16
- package/src/modes/controllers/event-controller.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +31 -1
- package/src/modes/controllers/goal-mode-controller.ts +79 -10
- package/src/modes/controllers/input-controller.ts +2 -0
- package/src/modes/controllers/plan-mode-controller.ts +49 -4
- package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
- package/src/modes/controllers/selector-controller.ts +14 -1
- package/src/modes/interactive-mode.ts +55 -4
- package/src/modes/prompt-suggestion-controller.ts +1 -1
- package/src/modes/runtime-init.ts +5 -1
- package/src/modes/utils/host-status.ts +21 -10
- package/src/prompts/agent-fragments/browser-aside.md +2 -2
- package/src/prompts/system/irc-incoming.md +3 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/runtime-mcp/manager.ts +8 -3
- package/src/runtime-mcp/project-host-values.ts +12 -8
- package/src/runtime-mcp/tool-bridge.ts +68 -4
- package/src/runtime-mcp/transports/stdio.ts +2 -1
- package/src/sdk/broker/broker-exit.ts +2 -0
- package/src/sdk/broker/broker.ts +143 -4
- package/src/sdk/broker/ensure.ts +74 -6
- package/src/sdk/broker/hop.ts +13 -1
- package/src/sdk/broker/lifecycle-ledger.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +102 -6
- package/src/sdk/broker/session-index.ts +29 -4
- package/src/sdk/broker/transport.ts +17 -1
- package/src/sdk/bus/chat-daemon-control.ts +2 -2
- package/src/sdk/bus/index.ts +408 -14
- package/src/sdk/bus/session-control.ts +2 -0
- package/src/sdk/bus/slack-daemon.ts +105 -9
- package/src/sdk/bus/telegram-daemon-contract.ts +7 -1
- package/src/sdk/bus/telegram-daemon.ts +10 -2
- package/src/sdk/bus/threaded-render.ts +20 -1
- package/src/sdk/cli/session-cli.ts +2 -0
- package/src/sdk/host/control/dispatch.ts +13 -1
- package/src/sdk/host/control/operations.ts +10 -1
- package/src/sdk/host/events.ts +67 -4
- package/src/sdk/host/host.ts +8 -2
- package/src/sdk/host/prompt-image-upload.ts +438 -0
- package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
- package/src/sdk/host/session-runtime.test.ts +1537 -24
- package/src/sdk/host/session-runtime.ts +332 -36
- package/src/sdk/host/surface-policy.ts +4 -0
- package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
- package/src/sdk/host/websocket-transport.ts +19 -7
- package/src/sdk/lifecycle/service.ts +14 -1
- package/src/sdk/prompt-status.ts +6 -4
- package/src/sdk/protocol/adapter-validation.ts +28 -1
- package/src/sdk/protocol/operation-inventory.generated.json +160 -0
- package/src/sdk/protocol/operation-registry.ts +33 -3
- package/src/sdk/session.ts +123 -36
- package/src/session/agent-session.ts +1614 -329
- package/src/session/history-storage.ts +182 -12
- package/src/session/internal/managed-session-scope.ts +47 -18
- package/src/session/internal/managed-session-storage.ts +71 -1
- package/src/session/session-manager.ts +90 -5
- package/src/session-import/redact.ts +53 -2
- package/src/skill-state/workflow-mutation-guard.ts +1498 -18
- package/src/slash-commands/builtin-registry.ts +6 -3
- package/src/slash-commands/helpers/mcp.ts +11 -3
- package/src/ssh/ssh-executor.ts +9 -2
- package/src/stt/recorder.ts +26 -9
- package/src/system-prompt.ts +61 -16
- package/src/task/executor.ts +12 -8
- package/src/task/role-body.ts +84 -0
- package/src/task/token-log-mode.test.ts +162 -0
- package/src/task/token-log.ts +91 -2
- package/src/tools/archive-reader.ts +650 -37
- package/src/tools/ask.ts +1 -0
- package/src/tools/ast-grep.ts +7 -2
- package/src/tools/auto-generated-guard.ts +12 -3
- package/src/tools/awaiting-input.ts +114 -0
- package/src/tools/bash-allowed-prefixes.ts +61 -1
- package/src/tools/bash-skill-urls.ts +58 -5
- package/src/tools/bash.ts +99 -26
- package/src/tools/browser/tab-worker.ts +39 -13
- package/src/tools/browser.ts +17 -7
- package/src/tools/cron.ts +10 -1
- package/src/tools/debug.ts +9 -2
- package/src/tools/descriptors.test.ts +4 -4
- package/src/tools/descriptors.ts +16 -4
- package/src/tools/eval.ts +15 -0
- package/src/tools/fetch.ts +90 -2
- package/src/tools/gh.ts +27 -12
- package/src/tools/image-gen.ts +20 -6
- package/src/tools/irc.ts +5 -3
- package/src/tools/monitor.ts +9 -1
- package/src/tools/output-meta.ts +51 -3
- package/src/tools/read.ts +9 -2
- package/src/tools/recipe/runner.ts +23 -12
- package/src/tools/render-utils.ts +2 -2
- package/src/tools/search.ts +3 -2
- package/src/tools/ssh.ts +274 -8
- package/src/tools/subagent.ts +10 -4
- package/src/tools/tmux-self-injection-guard.ts +3301 -122
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/tools/ultragoal-ask-guard.ts +13 -1
- package/src/tools/write.ts +7 -0
- package/src/tui/tree-list.ts +15 -1
- package/src/utils/commit-message-generator.ts +3 -2
- package/src/utils/external-editor.ts +21 -7
- package/src/utils/file-mentions.ts +46 -6
- package/src/utils/git.ts +38 -7
- package/src/utils/image-limits.ts +8 -0
- package/src/utils/image-loading.ts +1 -1
- package/src/utils/open.ts +73 -4
- package/src/utils/pasted-image-loading.ts +9 -8
- package/src/utils/prompt-suggestion.ts +5 -4
- package/src/utils/shell-snapshot.ts +9 -1
- package/src/utils/title-generator.ts +65 -20
- package/src/web/search/index.ts +2 -1
- package/src/web/search/providers/codex.ts +29 -32
package/src/export/ttsr.ts
CHANGED
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
* Manages rules that get injected mid-stream when their condition pattern matches
|
|
5
5
|
* the agent's output. When a match occurs, the stream is aborted, the rule is
|
|
6
6
|
* injected as a system reminder, and the request is retried.
|
|
7
|
+
*
|
|
8
|
+
* Rules may also carry `astCondition` ast-grep patterns, which are evaluated
|
|
9
|
+
* against the source a write/edit tool call introduces, before it executes.
|
|
7
10
|
*/
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { AstMatchStrictness, astMatch } from "@gajae-code/natives";
|
|
8
13
|
import { logger } from "@gajae-code/utils";
|
|
9
14
|
import type { Rule } from "../capability/rule";
|
|
10
15
|
import type { TtsrSettings } from "../config/settings";
|
|
@@ -22,6 +27,14 @@ export interface TtsrMatchContext {
|
|
|
22
27
|
streamKey?: string;
|
|
23
28
|
}
|
|
24
29
|
|
|
30
|
+
/** An astCondition pattern parse issue observed while checking a target language. */
|
|
31
|
+
export interface TtsrAstConditionDiagnostic {
|
|
32
|
+
ruleName: string;
|
|
33
|
+
lang: string;
|
|
34
|
+
patterns: string[];
|
|
35
|
+
parseErrors: string[];
|
|
36
|
+
}
|
|
37
|
+
|
|
25
38
|
interface ToolScope {
|
|
26
39
|
toolName?: string;
|
|
27
40
|
pathGlob?: Bun.Glob;
|
|
@@ -39,6 +52,7 @@ interface TtsrEntry {
|
|
|
39
52
|
rule: Rule;
|
|
40
53
|
conditions: RegExp[];
|
|
41
54
|
conditionWindows: Array<number | undefined>;
|
|
55
|
+
astConditions?: string[];
|
|
42
56
|
|
|
43
57
|
scope: TtsrScope;
|
|
44
58
|
globalPathGlobs?: Bun.Glob[];
|
|
@@ -69,6 +83,7 @@ const DEFAULT_SETTINGS: Required<TtsrSettings> = {
|
|
|
69
83
|
|
|
70
84
|
const SAFE_REGEX_WINDOW_CAP = 4096;
|
|
71
85
|
const SAFE_REGEX_WINDOW_MULTIPLIER = 4;
|
|
86
|
+
const SOURCE_SYNTAX_PARSE_ERROR = "parse error (syntax tree contains error nodes)";
|
|
72
87
|
|
|
73
88
|
const DEFAULT_SCOPE: TtsrScope = {
|
|
74
89
|
allowText: true,
|
|
@@ -81,7 +96,9 @@ export class TtsrManager {
|
|
|
81
96
|
readonly #settings: Required<TtsrSettings>;
|
|
82
97
|
readonly #rules = new Map<string, TtsrEntry>();
|
|
83
98
|
readonly #injectionRecords = new Map<string, InjectionRecord>();
|
|
99
|
+
readonly #astConditionDiagnostics = new Map<string, TtsrAstConditionDiagnostic>();
|
|
84
100
|
readonly #buffers = new Map<string, string>();
|
|
101
|
+
readonly #lastAstSnapshots = new Map<string, string>();
|
|
85
102
|
#messageCount = 0;
|
|
86
103
|
|
|
87
104
|
constructor(settings?: Partial<TtsrSettings>) {
|
|
@@ -339,7 +356,10 @@ export class TtsrManager {
|
|
|
339
356
|
}
|
|
340
357
|
|
|
341
358
|
const conditions = this.#compileConditions(rule);
|
|
342
|
-
|
|
359
|
+
const astConditions = rule.astCondition ?? [];
|
|
360
|
+
|
|
361
|
+
// Rule must have either regex or AST conditions
|
|
362
|
+
if (conditions.length === 0 && astConditions.length === 0) {
|
|
343
363
|
return false;
|
|
344
364
|
}
|
|
345
365
|
|
|
@@ -357,6 +377,7 @@ export class TtsrManager {
|
|
|
357
377
|
rule,
|
|
358
378
|
conditions,
|
|
359
379
|
conditionWindows,
|
|
380
|
+
astConditions: astConditions.length > 0 ? astConditions : undefined,
|
|
360
381
|
scope,
|
|
361
382
|
globalPathGlobs,
|
|
362
383
|
});
|
|
@@ -364,6 +385,7 @@ export class TtsrManager {
|
|
|
364
385
|
logger.debug("TTSR rule registered", {
|
|
365
386
|
ruleName: rule.name,
|
|
366
387
|
conditions: rule.condition,
|
|
388
|
+
astConditions: rule.astCondition,
|
|
367
389
|
scope: rule.scope,
|
|
368
390
|
globs: rule.globs,
|
|
369
391
|
});
|
|
@@ -446,6 +468,25 @@ export class TtsrManager {
|
|
|
446
468
|
return Array.from(this.#injectionRecords.keys());
|
|
447
469
|
}
|
|
448
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Undo a provisional claim whose reminder was never delivered: restore the
|
|
473
|
+
* record the rule had before the claim, or forget it when it had none, so the
|
|
474
|
+
* rule is eligible again exactly as before.
|
|
475
|
+
*/
|
|
476
|
+
releaseInjected(ruleName: string, previous: TtsrInjectionRecord | undefined): void {
|
|
477
|
+
const name = ruleName.trim();
|
|
478
|
+
if (name.length === 0) return;
|
|
479
|
+
if (!previous) {
|
|
480
|
+
this.#injectionRecords.delete(name);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.#injectionRecords.set(name, {
|
|
484
|
+
lastInjectedAt: previous.lastInjectedAt,
|
|
485
|
+
repeatMode: previous.repeatMode,
|
|
486
|
+
repeatGap: previous.repeatGap,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
449
490
|
/** Get full injected rule records (for persistence). */
|
|
450
491
|
getInjectedRecords(): TtsrInjectionRecord[] {
|
|
451
492
|
return Array.from(this.#injectionRecords, ([name, record]) => ({ name, ...record }));
|
|
@@ -477,6 +518,7 @@ export class TtsrManager {
|
|
|
477
518
|
/** Reset stream buffers (called on new turn). */
|
|
478
519
|
resetBuffer(): void {
|
|
479
520
|
this.#buffers.clear();
|
|
521
|
+
this.#lastAstSnapshots.clear();
|
|
480
522
|
}
|
|
481
523
|
|
|
482
524
|
/** Check if any TTSR rules are registered. */
|
|
@@ -509,4 +551,137 @@ export class TtsrManager {
|
|
|
509
551
|
getSettings(): Required<TtsrSettings> {
|
|
510
552
|
return this.#settings;
|
|
511
553
|
}
|
|
554
|
+
|
|
555
|
+
/** Check if any registered rule carries an `astCondition`. */
|
|
556
|
+
hasAstRules(): boolean {
|
|
557
|
+
if (this.#isDisabled()) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
for (const entry of this.#rules.values()) {
|
|
561
|
+
if (entry.astConditions) {
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/** Get observed astCondition pattern parse issues for rule diagnostics. */
|
|
569
|
+
getAstConditionDiagnostics(): TtsrAstConditionDiagnostic[] {
|
|
570
|
+
return Array.from(this.#astConditionDiagnostics.values(), diagnostic => ({
|
|
571
|
+
...diagnostic,
|
|
572
|
+
patterns: [...diagnostic.patterns],
|
|
573
|
+
parseErrors: [...diagnostic.parseErrors],
|
|
574
|
+
}));
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** Derive an ast-grep language alias from the candidate file extensions. */
|
|
578
|
+
#deriveLang(filePaths: string[] | undefined): string | undefined {
|
|
579
|
+
for (const filePath of filePaths ?? []) {
|
|
580
|
+
const ext = path.extname(this.#normalizePath(filePath));
|
|
581
|
+
if (ext.length > 1) return ext.slice(1).toLowerCase();
|
|
582
|
+
}
|
|
583
|
+
return undefined;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/** Match the source introduced by finalized tool arguments, before execution. */
|
|
587
|
+
async checkAstSnapshot(snapshot: string, context: TtsrMatchContext, signal?: AbortSignal): Promise<Rule[]> {
|
|
588
|
+
if (this.#isDisabled() || context.source !== "tool" || signal?.aborted) {
|
|
589
|
+
return [];
|
|
590
|
+
}
|
|
591
|
+
const lang = this.#deriveLang(context.filePaths);
|
|
592
|
+
if (!lang) return [];
|
|
593
|
+
|
|
594
|
+
const candidates: TtsrEntry[] = [];
|
|
595
|
+
for (const [name, entry] of this.#rules) {
|
|
596
|
+
if (!entry.astConditions?.length) continue;
|
|
597
|
+
if (
|
|
598
|
+
!this.#canTrigger(name) ||
|
|
599
|
+
!this.#matchesScope(entry, context) ||
|
|
600
|
+
!this.#matchesGlobalPaths(entry, context)
|
|
601
|
+
) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
candidates.push(entry);
|
|
605
|
+
}
|
|
606
|
+
if (candidates.length === 0) return [];
|
|
607
|
+
|
|
608
|
+
const bufferKey = this.#bufferKey(context);
|
|
609
|
+
if (this.#lastAstSnapshots.get(bufferKey) === snapshot) return [];
|
|
610
|
+
this.#lastAstSnapshots.set(bufferKey, snapshot);
|
|
611
|
+
|
|
612
|
+
const matches: Rule[] = [];
|
|
613
|
+
for (const entry of candidates) {
|
|
614
|
+
if (!entry.astConditions) continue;
|
|
615
|
+
const matched = await this.#astConditionsMatch(entry.rule.name, entry.astConditions, snapshot, lang, signal);
|
|
616
|
+
if (signal?.aborted) return [];
|
|
617
|
+
if (matched) {
|
|
618
|
+
matches.push(entry.rule);
|
|
619
|
+
logger.debug("TTSR ast condition matched", {
|
|
620
|
+
ruleName: entry.rule.name,
|
|
621
|
+
astConditions: entry.rule.astCondition,
|
|
622
|
+
toolName: context.toolName,
|
|
623
|
+
filePaths: context.filePaths,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return matches;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
#recordAstConditionDiagnostics(
|
|
631
|
+
ruleName: string,
|
|
632
|
+
patterns: string[],
|
|
633
|
+
lang: string,
|
|
634
|
+
parseErrors: string[] | undefined,
|
|
635
|
+
): void {
|
|
636
|
+
const patternParseErrors = parseErrors?.filter(parseError => parseError !== SOURCE_SYNTAX_PARSE_ERROR) ?? [];
|
|
637
|
+
if (patternParseErrors.length === 0) return;
|
|
638
|
+
|
|
639
|
+
const key = JSON.stringify([ruleName, lang]);
|
|
640
|
+
if (this.#astConditionDiagnostics.has(key)) return;
|
|
641
|
+
|
|
642
|
+
const diagnostic: TtsrAstConditionDiagnostic = {
|
|
643
|
+
ruleName,
|
|
644
|
+
lang,
|
|
645
|
+
patterns: [...patterns],
|
|
646
|
+
parseErrors: patternParseErrors,
|
|
647
|
+
};
|
|
648
|
+
this.#astConditionDiagnostics.set(key, diagnostic);
|
|
649
|
+
logger.warn("TTSR astCondition pattern is invalid for target language", {
|
|
650
|
+
ruleName,
|
|
651
|
+
lang,
|
|
652
|
+
patterns: diagnostic.patterns,
|
|
653
|
+
parseErrors: diagnostic.parseErrors,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async #astConditionsMatch(
|
|
658
|
+
ruleName: string,
|
|
659
|
+
patterns: string[],
|
|
660
|
+
source: string,
|
|
661
|
+
lang: string,
|
|
662
|
+
signal?: AbortSignal,
|
|
663
|
+
): Promise<boolean> {
|
|
664
|
+
try {
|
|
665
|
+
const result = await astMatch({
|
|
666
|
+
patterns,
|
|
667
|
+
source,
|
|
668
|
+
lang,
|
|
669
|
+
strictness: AstMatchStrictness.Smart,
|
|
670
|
+
limit: 1,
|
|
671
|
+
signal,
|
|
672
|
+
});
|
|
673
|
+
if (!signal?.aborted) {
|
|
674
|
+
this.#recordAstConditionDiagnostics(ruleName, patterns, lang, result.parseErrors);
|
|
675
|
+
}
|
|
676
|
+
return result.totalMatches > 0;
|
|
677
|
+
} catch (error) {
|
|
678
|
+
logger.warn("TTSR ast match failed, treating as no match", {
|
|
679
|
+
ruleName,
|
|
680
|
+
patterns,
|
|
681
|
+
lang,
|
|
682
|
+
error: error instanceof Error ? error.message : String(error),
|
|
683
|
+
});
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
512
687
|
}
|
|
@@ -15,6 +15,7 @@ import { loadCapability } from "../../discovery";
|
|
|
15
15
|
import { getExtensionNameFromPath } from "../../discovery/helpers";
|
|
16
16
|
import type { ExecOptions } from "../../exec/exec";
|
|
17
17
|
import { execCommand } from "../../exec/exec";
|
|
18
|
+
import type { QueuedRunStartFailure } from "../../session/agent-session";
|
|
18
19
|
import type { CustomMessage } from "../../session/messages";
|
|
19
20
|
import { EventBus } from "../../utils/event-bus";
|
|
20
21
|
import { installLegacyPiSpecifierShim, loadLegacyPiModule } from "../plugins/legacy-pi-compat";
|
|
@@ -394,6 +395,7 @@ class ConcreteExtensionAPI implements ExtensionAPI {
|
|
|
394
395
|
onPreflightAccepted?: () => void;
|
|
395
396
|
onPreflightAcceptCommit?: () => void | Promise<void>;
|
|
396
397
|
onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
398
|
+
onQueuedRunStartFailed?: (failure: QueuedRunStartFailure) => void;
|
|
397
399
|
onDispatchDisposition?: (promotion: { startsOwnRun: boolean }) => void;
|
|
398
400
|
preflightSignal?: AbortSignal;
|
|
399
401
|
},
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { ptree } from "@gajae-code/utils";
|
|
2
|
+
import { $credentialEnv, startupProjectEnvSnapshot } from "@gajae-code/utils/env";
|
|
3
|
+
import { canonicalEnvKey } from "@gajae-code/utils/env-file";
|
|
4
|
+
import type { ExecResult } from "../../exec/exec";
|
|
1
5
|
import type { MCPServerConnection, MCPToolCallResult } from "../../runtime-mcp";
|
|
2
6
|
import { callTool, connectToServer, disconnectServer } from "../../runtime-mcp";
|
|
3
7
|
import { createExactPrefixCommandBridge } from "./prefix-command-bridge";
|
|
4
8
|
import type { ExtensionContext, InputEvent, InputEventResult } from "./types";
|
|
5
9
|
|
|
6
10
|
const OUROBOROS_CLI_ENV = "OUROBOROS_CLI";
|
|
11
|
+
const DEFAULT_OUROBOROS_COMMAND = "ouroboros";
|
|
7
12
|
const INTERVIEW_COMMAND = "ooo interview";
|
|
8
13
|
const INTERVIEW_TOOL = "ouroboros_interview";
|
|
9
14
|
const INTERVIEW_SESSION_PATTERN = /^interview_[A-Za-z0-9_-]+$/;
|
|
@@ -23,8 +28,64 @@ interface OuroborosOooBridgeHandler {
|
|
|
23
28
|
reset(): Promise<void>;
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
function projectSuppliedOuroborosCli(): boolean {
|
|
32
|
+
const snapshot = startupProjectEnvSnapshot();
|
|
33
|
+
const key = canonicalEnvKey(OUROBOROS_CLI_ENV);
|
|
34
|
+
return Object.hasOwn(snapshot.values, key) || snapshot.dynamic.has(key);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Executable for `ooo` dispatch and the interview MCP server.
|
|
39
|
+
*
|
|
40
|
+
* The string is spawned directly, so a repository `.env` must not choose it.
|
|
41
|
+
* `$credentialEnv` keeps a launching-shell or user-owned setting and ignores
|
|
42
|
+
* the project dotenv, including a dynamic declaration. Anything else stays the
|
|
43
|
+
* default command name. A rejected project value is removed from `process.env`
|
|
44
|
+
* before either spawn. Prefix dispatch also passes that cleaned object: Bun
|
|
45
|
+
* keeps the startup environment for a spawn that omits `env`, so deleting the
|
|
46
|
+
* key alone would still hand the project value to the child.
|
|
47
|
+
*/
|
|
26
48
|
function resolveOuroborosCommand(): string {
|
|
27
|
-
|
|
49
|
+
const trusted = $credentialEnv(OUROBOROS_CLI_ENV)?.trim();
|
|
50
|
+
if (trusted) {
|
|
51
|
+
const live = process.env[OUROBOROS_CLI_ENV];
|
|
52
|
+
if (live !== undefined && live.trim() !== trusted) process.env[OUROBOROS_CLI_ENV] = trusted;
|
|
53
|
+
return trusted;
|
|
54
|
+
}
|
|
55
|
+
if (projectSuppliedOuroborosCli()) delete process.env[OUROBOROS_CLI_ENV];
|
|
56
|
+
return DEFAULT_OUROBOROS_COMMAND;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ouroborosSpawnEnv(): Record<string, string> {
|
|
60
|
+
resolveOuroborosCommand();
|
|
61
|
+
const env: Record<string, string> = {};
|
|
62
|
+
for (const key of Object.keys(process.env)) {
|
|
63
|
+
const value = process.env[key];
|
|
64
|
+
if (typeof value === "string") env[key] = value;
|
|
65
|
+
}
|
|
66
|
+
return env;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function dispatchOuroborosCommand(
|
|
70
|
+
command: string,
|
|
71
|
+
args: string[],
|
|
72
|
+
ctx: ExtensionContext,
|
|
73
|
+
execOptions: { timeout?: number },
|
|
74
|
+
): Promise<ExecResult> {
|
|
75
|
+
const result = await ptree.exec([command, ...args], {
|
|
76
|
+
cwd: ctx.cwd,
|
|
77
|
+
timeout: execOptions.timeout,
|
|
78
|
+
allowNonZero: true,
|
|
79
|
+
allowAbort: true,
|
|
80
|
+
stderr: "full",
|
|
81
|
+
env: ouroborosSpawnEnv(),
|
|
82
|
+
});
|
|
83
|
+
return {
|
|
84
|
+
stdout: result.stdout,
|
|
85
|
+
stderr: result.stderr,
|
|
86
|
+
code: result.exitCode ?? 0,
|
|
87
|
+
killed: Boolean(result.exitError?.aborted),
|
|
88
|
+
};
|
|
28
89
|
}
|
|
29
90
|
|
|
30
91
|
function interviewArgument(text: string): string | undefined {
|
|
@@ -89,6 +150,7 @@ export function createOuroborosOooBridge(options: OuroborosOooBridgeOptions = {}
|
|
|
89
150
|
prefix: "ooo",
|
|
90
151
|
command: resolveOuroborosCommand(),
|
|
91
152
|
args: ["dispatch", "--runtime", "gjc"],
|
|
153
|
+
dispatch: dispatchOuroborosCommand,
|
|
92
154
|
});
|
|
93
155
|
|
|
94
156
|
function assertCurrent(generation: number, signal: AbortSignal | undefined): void {
|
|
@@ -50,7 +50,7 @@ import type { CustomEditor } from "../../modes/components/custom-editor";
|
|
|
50
50
|
import type { WorkflowGateEmitter } from "../../modes/shared/agent-wire/workflow-gate-broker";
|
|
51
51
|
import type { Theme } from "../../modes/theme/theme";
|
|
52
52
|
import type { ProjectProgressSnapshot } from "../../progress/progress-contract";
|
|
53
|
-
import type { AgentSessionEventListener } from "../../session/agent-session";
|
|
53
|
+
import type { AgentSessionEventListener, QueuedRunStartFailure } from "../../session/agent-session";
|
|
54
54
|
import type {
|
|
55
55
|
ClientBridge,
|
|
56
56
|
ClientBridgePermissionOption,
|
|
@@ -1347,6 +1347,8 @@ export interface ExtensionAPI {
|
|
|
1347
1347
|
onPreflightAccepted?: () => void;
|
|
1348
1348
|
onPreflightAcceptCommit?: () => void | Promise<void>;
|
|
1349
1349
|
onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
1350
|
+
/** Internal positive refusal before an exact queued invocation can start. */
|
|
1351
|
+
onQueuedRunStartFailed?: (failure: QueuedRunStartFailure) => void;
|
|
1350
1352
|
onDispatchDisposition?: (promotion: { startsOwnRun: boolean }) => void;
|
|
1351
1353
|
preflightSignal?: AbortSignal;
|
|
1352
1354
|
/** Internal SDK correlation owner for an exact queued follow-up. */
|
|
@@ -1577,6 +1579,8 @@ export type SendUserMessageHandler = (
|
|
|
1577
1579
|
* grants root abort ownership.
|
|
1578
1580
|
*/
|
|
1579
1581
|
onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean }) => void;
|
|
1582
|
+
/** Internal positive refusal, not an inference from a dispatch error or missing event. */
|
|
1583
|
+
onQueuedRunStartFailed?: (failure: QueuedRunStartFailure) => void;
|
|
1580
1584
|
/**
|
|
1581
1585
|
* Fired synchronously when a plain prompt's dispatch resolves at queue
|
|
1582
1586
|
* time instead of run end (diverted into an in-flight run's steering).
|
|
@@ -346,9 +346,17 @@ export async function discoverAndLoadHooks(configuredPaths: string[], cwd: strin
|
|
|
346
346
|
};
|
|
347
347
|
|
|
348
348
|
// 1. Discover hooks via capability API and validate the provider descriptor
|
|
349
|
-
// against the canonical model before importing
|
|
349
|
+
// against the canonical model before importing. A repository
|
|
350
|
+
// `<cwd>/.gjc/hooks` module is not imported. Skip those entries before
|
|
351
|
+
// dedup so a same-named user-agent hook still loads. An explicit path
|
|
352
|
+
// below remains a consent path.
|
|
350
353
|
const discovered = await loadCapability<Hook>(hookCapability.id, { cwd, providers: ["native"] });
|
|
351
|
-
|
|
354
|
+
const seenHookKeys = new Set<string>();
|
|
355
|
+
for (const hook of discovered.all) {
|
|
356
|
+
if (hook.level === "project") continue;
|
|
357
|
+
const hookKey = `${hook.type}:${hook.tool}:${hook.name}`;
|
|
358
|
+
if (seenHookKeys.has(hookKey)) continue;
|
|
359
|
+
seenHookKeys.add(hookKey);
|
|
352
360
|
const convention =
|
|
353
361
|
hook._source.provider === "native"
|
|
354
362
|
? HookSourceConvention.NativeGjc
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type NativeDirectoryTreeSnapshot,
|
|
16
16
|
type NativeExactUnlinkResult,
|
|
17
17
|
renameDirectoryNoReplacePathAsync,
|
|
18
|
+
renameNoReplacePathAsync,
|
|
18
19
|
snapshotDirectoryTree,
|
|
19
20
|
} from "@gajae-code/natives";
|
|
20
21
|
import { isEnoent, logger, pathIsWithin } from "@gajae-code/utils";
|
|
@@ -175,6 +176,13 @@ async function snapshotIfPresent(targetPath: string): Promise<NativeDirectoryTre
|
|
|
175
176
|
return snapshot.ok ? snapshot.snapshot : undefined;
|
|
176
177
|
}
|
|
177
178
|
|
|
179
|
+
/** Windows supports directory no-replace through the handle-based path API, not the POSIX directory fallback. */
|
|
180
|
+
function renameDirectoryNoReplace(sourcePath: string, destinationPath: string) {
|
|
181
|
+
return process.platform === "win32"
|
|
182
|
+
? renameNoReplacePathAsync(sourcePath, destinationPath)
|
|
183
|
+
: renameDirectoryNoReplacePathAsync(sourcePath, destinationPath);
|
|
184
|
+
}
|
|
185
|
+
|
|
178
186
|
/**
|
|
179
187
|
* Full identity comparison between two directory tree snapshots. A same-
|
|
180
188
|
* filesystem `renameat`-class namespace operation changes neither the root's
|
|
@@ -850,9 +858,10 @@ export class MarketplaceManager {
|
|
|
850
858
|
});
|
|
851
859
|
if (finalStat?.isSymbolicLink()) throw new MarketplaceRestoreError("final_path_is_symlink");
|
|
852
860
|
const hadFinal = finalStat !== undefined;
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
const
|
|
861
|
+
// Keep the private backup component bounded independently of the canonical
|
|
862
|
+
// cache basename, which can itself be near NAME_MAX through a long version.
|
|
863
|
+
const backupName = `.gjc-restore-backup-${createHash("sha256").update(finalPath).digest("hex")}-${randomBytes(12).toString("hex")}`;
|
|
864
|
+
const backup = path.join(pluginsCacheDir, backupName);
|
|
856
865
|
|
|
857
866
|
const parentRecheck = await fs.lstat(pluginsCacheDir, { bigint: true }).catch(() => undefined);
|
|
858
867
|
if (!parentRecheck || parentRecheck.dev !== parentBefore.dev || parentRecheck.ino !== parentBefore.ino)
|
|
@@ -878,12 +887,29 @@ export class MarketplaceManager {
|
|
|
878
887
|
// merely a decision about whether it is safe to proceed. sideEffectStarted
|
|
879
888
|
// stays false — effects are physical, not procedural.
|
|
880
889
|
if (!finalSnapshotBefore) throw new MarketplaceRestoreError("final_path_snapshot_unavailable");
|
|
881
|
-
const detached = exactRemoveDirectoryTree(
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
890
|
+
const detached = exactRemoveDirectoryTree(
|
|
891
|
+
finalPath,
|
|
892
|
+
finalSnapshotBefore,
|
|
893
|
+
{
|
|
894
|
+
dev: parentBefore.dev,
|
|
895
|
+
ino: parentBefore.ino,
|
|
896
|
+
},
|
|
897
|
+
true,
|
|
898
|
+
);
|
|
885
899
|
const detachedPath = detachedTree(detached);
|
|
886
900
|
if (!detachedPath) {
|
|
901
|
+
if (detached.code === "restore_failed" && detached.detachedPath) {
|
|
902
|
+
const retainedSnapshot = await snapshotIfPresent(detached.detachedPath).catch(() => undefined);
|
|
903
|
+
const retainedBackupPath =
|
|
904
|
+
retainedSnapshot && sameDirectoryTreeIdentity(finalSnapshotBefore, retainedSnapshot)
|
|
905
|
+
? detached.detachedPath
|
|
906
|
+
: undefined;
|
|
907
|
+
throw new MarketplaceRestoreError("backup_detach_unverified", {
|
|
908
|
+
sideEffectStarted: true,
|
|
909
|
+
outcome: "uncertain",
|
|
910
|
+
...(retainedBackupPath ? { retainedBackupPath } : {}),
|
|
911
|
+
});
|
|
912
|
+
}
|
|
887
913
|
// identity_mismatch means finalPath already changed before we ever
|
|
888
914
|
// touched it — exactRemoveDirectoryTree refused the mutation entirely,
|
|
889
915
|
// so this is a pure precheck refusal: nothing moved, sideEffectStarted
|
|
@@ -906,7 +932,7 @@ export class MarketplaceManager {
|
|
|
906
932
|
// later detach of the same canonical path inside this same transaction (the
|
|
907
933
|
// rollback path) collides with our own leftover and is misread as a foreign
|
|
908
934
|
// claim. The rename below is what vacates it; failure is reported, never assumed.
|
|
909
|
-
const backupPublish = await
|
|
935
|
+
const backupPublish = await renameDirectoryNoReplace(detachedPath, backup);
|
|
910
936
|
if (!backupPublish.ok) {
|
|
911
937
|
// mutationState "not_committed" proves the rename never moved the
|
|
912
938
|
// object; it is still at the detached path, so that path is real
|
|
@@ -962,7 +988,7 @@ export class MarketplaceManager {
|
|
|
962
988
|
// devices on cross_device, the dev/ino-stability assumption behind
|
|
963
989
|
// `stagedSnapshot` breaks silently and every identity-bound check in this
|
|
964
990
|
// method becomes unsound.
|
|
965
|
-
const publish = await
|
|
991
|
+
const publish = await renameDirectoryNoReplace(token.stagedArtifactPath, finalPath);
|
|
966
992
|
// Test seam only (no-op in production): lets a test deterministically
|
|
967
993
|
// simulate a concurrent second writer republishing finalPath here,
|
|
968
994
|
// exercising the foreign-owner branch below without weakening it.
|
|
@@ -1233,7 +1259,7 @@ export class MarketplaceManager {
|
|
|
1233
1259
|
);
|
|
1234
1260
|
}
|
|
1235
1261
|
}
|
|
1236
|
-
const restorePublish = await
|
|
1262
|
+
const restorePublish = await renameDirectoryNoReplace(backup, finalPath);
|
|
1237
1263
|
if (!restorePublish.ok) {
|
|
1238
1264
|
// Restore failed: confirm what actually remains at `backup` right now
|
|
1239
1265
|
// (the no-replace rename may have left it there proven-untouched, or
|
|
@@ -116,6 +116,11 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<
|
|
|
116
116
|
return cmd;
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
+
export function getBuiltinSlashCommandsForSession(hasExactMcpControls: boolean): typeof BUILTIN_SLASH_COMMANDS {
|
|
120
|
+
if (hasExactMcpControls) return BUILTIN_SLASH_COMMANDS;
|
|
121
|
+
return BUILTIN_SLASH_COMMANDS.filter(command => command.name !== "mcp");
|
|
122
|
+
}
|
|
123
|
+
|
|
119
124
|
/**
|
|
120
125
|
* Represents a custom slash command loaded from a file
|
|
121
126
|
*/
|
|
@@ -2299,7 +2299,10 @@ async function removeGcDiskEntry(
|
|
|
2299
2299
|
return { removed: false, reason: "entry_changed" };
|
|
2300
2300
|
}
|
|
2301
2301
|
const retainedAtOriginal = !removal.detachedPath || path.resolve(removal.detachedPath) === path.resolve(target);
|
|
2302
|
-
if (
|
|
2302
|
+
if (
|
|
2303
|
+
(removal.code === "permission_denied" || removal.code === "access_denied" || removal.code === "io_error") &&
|
|
2304
|
+
retainedAtOriginal
|
|
2305
|
+
) {
|
|
2303
2306
|
return { removed: false, reason: "withheld_evidence_incomplete: tree_unreadable", withheld: true };
|
|
2304
2307
|
}
|
|
2305
2308
|
return {
|
|
@@ -71,11 +71,13 @@ import {
|
|
|
71
71
|
} from "./tmux-owner-isolation";
|
|
72
72
|
import { assertGjcTmuxStagedMutationAuthoritySync } from "./tmux-provider-context";
|
|
73
73
|
import {
|
|
74
|
+
childGjcSessionIdAssignment,
|
|
74
75
|
findGjcTmuxSessionByName,
|
|
75
76
|
findGjcTmuxSessionByScope,
|
|
76
77
|
type GjcTmuxSessionStatus,
|
|
77
78
|
type ProvenTmuxSessionIdentity,
|
|
78
79
|
proveGjcTmuxSessionMutationTarget,
|
|
80
|
+
tmuxLaunchIdentityEnv,
|
|
79
81
|
} from "./tmux-sessions";
|
|
80
82
|
import {
|
|
81
83
|
buildWindowsPowerShellInnerCommand,
|
|
@@ -433,6 +435,8 @@ function stripRootTmuxFlag(rawArgs: string[]): string[] {
|
|
|
433
435
|
|
|
434
436
|
export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProfileResult {
|
|
435
437
|
const env = context.env ?? process.env;
|
|
438
|
+
const cwd = context.cwd ?? process.cwd();
|
|
439
|
+
const identityEnv = tmuxLaunchIdentityEnv(env, cwd);
|
|
436
440
|
const branchSlug = context.branch ? buildGjcTmuxSessionSlug(context.branch) : (context.branchSlug ?? null);
|
|
437
441
|
// The psmux UX filter (mouse / set-clipboard / mode-style /
|
|
438
442
|
// set-window-option) now lives in buildGjcTmuxProfileCommands so every
|
|
@@ -448,8 +452,8 @@ export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProf
|
|
|
448
452
|
branch: context.branch ?? null,
|
|
449
453
|
branchSlug,
|
|
450
454
|
project: context.project ?? null,
|
|
451
|
-
sessionId: context.sessionId ??
|
|
452
|
-
sessionStateFile: context.sessionStateFile ??
|
|
455
|
+
sessionId: context.sessionId ?? identityEnv[GJC_COORDINATOR_SESSION_ID_ENV] ?? null,
|
|
456
|
+
sessionStateFile: context.sessionStateFile ?? identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV] ?? null,
|
|
453
457
|
ownerGeneration: context.ownerGeneration ?? null,
|
|
454
458
|
ownerServerKey: context.ownerServerKey ?? null,
|
|
455
459
|
version: context.version ?? null,
|
|
@@ -463,7 +467,6 @@ export function applyGjcTmuxProfile(context: GjcTmuxProfileContext): GjcTmuxProf
|
|
|
463
467
|
});
|
|
464
468
|
if (commands.length === 0) return { skipped: true, commands: [], failures: [] };
|
|
465
469
|
const spawnSync = context.spawnSync ?? defaultSpawnSync;
|
|
466
|
-
const cwd = context.cwd ?? process.cwd();
|
|
467
470
|
const options: TmuxSpawnOptions = {
|
|
468
471
|
cwd,
|
|
469
472
|
env,
|
|
@@ -1106,22 +1109,24 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
1106
1109
|
if (policy === "tmux" && !isInteractiveRootLaunch(context.parsed, tty)) return undefined;
|
|
1107
1110
|
|
|
1108
1111
|
const cwd = context.cwd ?? process.cwd();
|
|
1112
|
+
const identityEnv = tmuxLaunchIdentityEnv(env, cwd);
|
|
1109
1113
|
const branch = context.worktreeBranch ?? context.currentBranch ?? readCurrentBranch(cwd);
|
|
1110
1114
|
const project = context.project ?? cwd;
|
|
1111
|
-
const sessionName = buildGjcTmuxSessionName(
|
|
1115
|
+
const sessionName = buildGjcTmuxSessionName(identityEnv, { branch });
|
|
1112
1116
|
// Pick the most appropriate tmux binary for this platform. On native Windows
|
|
1113
1117
|
// the resolver walks psmux / pmux / tmux and uses the first one present on
|
|
1114
1118
|
// PATH, so the default `gjc --tmux` flow lands on a real multiplexer even
|
|
1115
1119
|
// without an explicit GJC_TMUX_COMMAND override.
|
|
1116
1120
|
const resolvedBinary = resolveGjcTmuxBinary({ platform, env });
|
|
1117
1121
|
const tmuxCommand = resolvedBinary.command;
|
|
1118
|
-
const sessionId =
|
|
1122
|
+
const sessionId = identityEnv[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || sessionName;
|
|
1119
1123
|
// The session ROOT is keyed by the active GJC session (GJC_SESSION_ID), NOT the
|
|
1120
1124
|
// coordinator/tmux identity. Fall back to the coordinator id only for standalone
|
|
1121
|
-
// tmux launches with no GJC session context.
|
|
1122
|
-
|
|
1125
|
+
// tmux launches with no GJC session context. Project dotenv pins are already
|
|
1126
|
+
// removed from identityEnv.
|
|
1127
|
+
const gjcSessionId = identityEnv.GJC_SESSION_ID?.trim() || sessionId;
|
|
1123
1128
|
const sessionStateFile =
|
|
1124
|
-
|
|
1129
|
+
identityEnv[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim() ||
|
|
1125
1130
|
tmuxRuntimeSessionPath(cwd, gjcSessionId, buildGjcTmuxSessionSlug(sessionName));
|
|
1126
1131
|
const signingEnv = coordinatorSidecarSigningEnv(env);
|
|
1127
1132
|
// Stage only the one-shot channel descriptor (path + key id). The PKCS#8 key
|
|
@@ -1136,13 +1141,13 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
1136
1141
|
(context.diagnosticWriter ?? safeStderrWrite)(formatTmuxUnavailableDiagnostic(platform));
|
|
1137
1142
|
return undefined;
|
|
1138
1143
|
}
|
|
1139
|
-
const existingSessionName = allowsExistingTmuxAttach(context.parsed,
|
|
1144
|
+
const existingSessionName = allowsExistingTmuxAttach(context.parsed, identityEnv)
|
|
1140
1145
|
? "existingBranchSessionName" in context
|
|
1141
1146
|
? (context.existingBranchSessionName ?? undefined)
|
|
1142
1147
|
: resolvedBinary.isPsmux && platform === "win32"
|
|
1143
|
-
? explicitTmuxSessionName(
|
|
1148
|
+
? explicitTmuxSessionName(identityEnv)
|
|
1144
1149
|
: findExistingSessionForLaunch({
|
|
1145
|
-
env,
|
|
1150
|
+
env: identityEnv,
|
|
1146
1151
|
project,
|
|
1147
1152
|
branch,
|
|
1148
1153
|
})
|
|
@@ -1154,6 +1159,7 @@ export function buildDefaultTmuxLaunchPlan(context: TmuxLaunchContext): TmuxLaun
|
|
|
1154
1159
|
execPath: context.execPath ?? process.execPath,
|
|
1155
1160
|
extraEnv: {
|
|
1156
1161
|
...childSigningEnv,
|
|
1162
|
+
...childGjcSessionIdAssignment(env, identityEnv, cwd, gjcSessionId),
|
|
1157
1163
|
[GJC_COORDINATOR_SESSION_ID_ENV]: sessionId,
|
|
1158
1164
|
[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: sessionStateFile,
|
|
1159
1165
|
// Carry the GJC-managed session name into the child so tmux-backed
|
|
@@ -1210,6 +1216,9 @@ function trustedReplacementAuthority(
|
|
|
1210
1216
|
function prepareManagedOwnerLifecycle(plan: TmuxLaunchPlan, context: TmuxLaunchContext): void {
|
|
1211
1217
|
if (plan.ownerGeneration) return;
|
|
1212
1218
|
const sessionId = plan.sessionId ?? plan.sessionName;
|
|
1219
|
+
const launchEnv = context.env ?? process.env;
|
|
1220
|
+
const identityEnv = tmuxLaunchIdentityEnv(launchEnv, plan.cwd);
|
|
1221
|
+
const gjcSessionId = identityEnv.GJC_SESSION_ID?.trim() || sessionId;
|
|
1213
1222
|
const stateDir = path.dirname(plan.sessionStateFile ?? path.join(plan.cwd, ".gjc", "runtime"));
|
|
1214
1223
|
const baseline = captureOwnerGenerationBaselineSync(stateDir, sessionId);
|
|
1215
1224
|
const replacement = trustedReplacementAuthority(stateDir, sessionId, baseline);
|
|
@@ -1234,6 +1243,7 @@ function prepareManagedOwnerLifecycle(plan: TmuxLaunchPlan, context: TmuxLaunchC
|
|
|
1234
1243
|
[GJC_COORDINATOR_SIDECAR_KEY_ID_ENV]: plan.coordinatorSidecarBootstrap.keyId,
|
|
1235
1244
|
}
|
|
1236
1245
|
: {}),
|
|
1246
|
+
...childGjcSessionIdAssignment(launchEnv, identityEnv, plan.cwd, gjcSessionId),
|
|
1237
1247
|
[GJC_COORDINATOR_SESSION_ID_ENV]: sessionId,
|
|
1238
1248
|
[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: plan.sessionStateFile ?? "",
|
|
1239
1249
|
// The tmux server inherits the bootstrap secret as child environment;
|
|
@@ -1527,7 +1537,7 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
1527
1537
|
try {
|
|
1528
1538
|
if (plan.attachSessionName) {
|
|
1529
1539
|
const stateDir = env[GJC_TMUX_OWNER_STATE_DIR_ENV]?.trim();
|
|
1530
|
-
const sessionId = env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim();
|
|
1540
|
+
const sessionId = tmuxLaunchIdentityEnv(env, plan.cwd)[GJC_COORDINATOR_SESSION_ID_ENV]?.trim();
|
|
1531
1541
|
const generation = env[GJC_TMUX_OWNER_GENERATION_ENV]?.trim();
|
|
1532
1542
|
if (!stateDir || !sessionId || !generation) throw new Error("gjc_tmux_provider_authority_unavailable");
|
|
1533
1543
|
plan.authority = readGjcTmuxProviderAuthoritySync({ stateDir, sessionId, generation });
|
|
@@ -1596,7 +1606,7 @@ export function launchDefaultTmuxIfNeeded(context: TmuxLaunchContext): boolean {
|
|
|
1596
1606
|
}
|
|
1597
1607
|
};
|
|
1598
1608
|
const creationSpawn = plan.authority ? spawnSync : rawSpawnSync;
|
|
1599
|
-
const spawnEnv = { ...env };
|
|
1609
|
+
const spawnEnv = { ...tmuxLaunchIdentityEnv(env, plan.cwd) };
|
|
1600
1610
|
delete spawnEnv[GJC_COORDINATOR_SIDECAR_SIGNING_KEY_ENV];
|
|
1601
1611
|
const signingEnv = coordinatorSidecarSigningEnv(env);
|
|
1602
1612
|
Object.assign(spawnEnv, signingEnv);
|