@gajae-code/coding-agent 0.11.6 → 0.11.8
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 +92 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-activation.d.ts +4 -3
- package/dist/types/config/model-profile-contract.d.ts +38 -0
- package/dist/types/config/settings-schema.d.ts +70 -7
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/main.d.ts +32 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +2 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +4 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/manager.d.ts +1 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +26 -1
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +1 -1
- package/dist/types/sdk/host/host.d.ts +1 -1
- package/dist/types/sdk/host/query/handlers.d.ts +9 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/startup-capability.d.ts +3 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +30 -5
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
- package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/session-manager.d.ts +25 -4
- package/dist/types/session/session-storage.d.ts +18 -0
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +94 -0
- package/dist/types/tools/ask.d.ts +454 -174
- package/dist/types/tools/fetch.d.ts +5 -8
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -8
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/scripts/run-test-manifest.ts +4 -4
- package/src/capability/index.ts +50 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +2 -92
- package/src/cli/notify-cli.ts +5 -38
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +52 -9
- package/src/commands/daemon.ts +22 -5
- package/src/commands/sdk.ts +44 -11
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-activation.ts +7 -18
- package/src/config/model-profile-contract.ts +161 -0
- package/src/config/model-profiles.ts +18 -0
- package/src/config/model-registry.ts +11 -1
- package/src/config/settings-schema.ts +73 -7
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
- package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/py/executor.ts +30 -11
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/gjc-runtime/launch-tmux.ts +1 -1
- package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/gjc-runtime/ralplan-runtime.ts +75 -3
- package/src/gjc-runtime/repository-binding.ts +267 -0
- package/src/gjc-runtime/session-state-sidecar.ts +25 -9
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
- package/src/gjc-runtime/tmux-sessions.ts +45 -6
- package/src/gjc-runtime/ultragoal-guard.ts +93 -15
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
- package/src/gjc-runtime/workflow-manifest.ts +23 -0
- package/src/internal-urls/docs-index.generated.ts +12 -10
- package/src/internal-urls/local-protocol.ts +64 -21
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +111 -37
- package/src/modes/DESIGN.md +5 -0
- package/src/modes/acp/acp-agent.ts +50 -16
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/model-selector.ts +73 -11
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/tool-status-header.ts +25 -4
- package/src/modes/components/welcome.ts +22 -18
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +97 -54
- package/src/modes/controllers/event-controller.ts +10 -0
- package/src/modes/controllers/input-controller.ts +38 -2
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +149 -63
- package/src/modes/interactive-mode.ts +57 -34
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +4 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +5 -3
- package/src/prompts/agents/architect.md +0 -1
- package/src/prompts/agents/critic.md +0 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -4
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/json-rpc.ts +23 -9
- package/src/runtime-mcp/manager.ts +51 -53
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/lifecycle.ts +185 -36
- package/src/sdk/bus/chat-daemon-control.ts +19 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +9 -1
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/index.ts +244 -70
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +68 -20
- package/src/sdk/bus/prompt-reconciliation.ts +239 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
- package/src/sdk/bus/telegram-daemon-control.ts +147 -35
- package/src/sdk/bus/telegram-daemon.ts +2463 -450
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +1 -1
- package/src/sdk/host/control/operations.ts +1 -1
- package/src/sdk/host/host.ts +2 -6
- package/src/sdk/host/query/handlers.ts +70 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +58 -0
- package/src/sdk/protocol/operation-registry.ts +18 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +31 -5
- package/src/sdk/startup-capability.ts +9 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +292 -154
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/internal/managed-session-scope.ts +1163 -137
- package/src/session/internal/managed-session-storage.ts +397 -234
- package/src/session/internal/native-publish-outcome.ts +266 -0
- package/src/session/session-manager.ts +620 -403
- package/src/session/session-storage.ts +128 -11
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +30 -1
- package/src/setup/provider-presets.json +2 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/gjc-command.ts +2 -2
- package/src/task/index.ts +121 -15
- package/src/task/receipt.ts +3 -0
- package/src/task/types.ts +34 -3
- package/src/tools/ask.ts +199 -89
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +13 -131
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +4 -0
- package/src/tools/read.ts +2 -0
- package/src/tools/render-utils.ts +7 -1
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
|
@@ -259,6 +259,19 @@ export interface VerifiedSessionDeleteTarget {
|
|
|
259
259
|
detachedArtifactsPath?: string;
|
|
260
260
|
/** Identity-bound quarantine path retained when transcript unlink deferred cleanup. */
|
|
261
261
|
detachedTranscriptPath?: string;
|
|
262
|
+
/** Native-retained publisher successor observed during transcript cleanup. */
|
|
263
|
+
retainedTranscriptSuccessorPath?: string;
|
|
264
|
+
/** Native-retained exchange placeholder observed during transcript cleanup. */
|
|
265
|
+
retainedTranscriptPlaceholderPath?: string;
|
|
266
|
+
/** Native-retained transcript entry whose identity could not be verified. */
|
|
267
|
+
retainedTranscriptUnknownPath?: string;
|
|
268
|
+
/** Native-retained publisher successor observed during cleanup. */
|
|
269
|
+
retainedArtifactsSuccessorPath?: string;
|
|
270
|
+
/** Native-retained exchange placeholder observed during cleanup. */
|
|
271
|
+
retainedArtifactsPlaceholderPath?: string;
|
|
272
|
+
/** Native-retained entry whose identity could not be verified. */
|
|
273
|
+
retainedArtifactsUnknownPath?: string;
|
|
274
|
+
|
|
262
275
|
/** Caller-published, no-replace quarantine pathname for the next artifact detach. */
|
|
263
276
|
plannedArtifactsPath?: string;
|
|
264
277
|
/** Caller-published, no-replace quarantine pathname for the next transcript detach. */
|
|
@@ -288,6 +301,9 @@ export type VerifiedSessionDeleteResult =
|
|
|
288
301
|
artifactsTree: NativeDirectoryTreeSnapshot;
|
|
289
302
|
/** Transcript identity (unchanged) for retry binding. */
|
|
290
303
|
transcriptIdentity: SessionStorageFileIdentity;
|
|
304
|
+
retainedSuccessorPath?: string;
|
|
305
|
+
retainedPlaceholderPath?: string;
|
|
306
|
+
retainedUnknownPath?: string;
|
|
291
307
|
}
|
|
292
308
|
| {
|
|
293
309
|
kind: "cleanup_pending";
|
|
@@ -297,6 +313,9 @@ export type VerifiedSessionDeleteResult =
|
|
|
297
313
|
transcriptIdentity: SessionStorageFileIdentity;
|
|
298
314
|
/** Optional identity-bound transcript quarantine path for restart cleanup. */
|
|
299
315
|
detachedTranscriptPath?: string;
|
|
316
|
+
retainedSuccessorPath?: string;
|
|
317
|
+
retainedPlaceholderPath?: string;
|
|
318
|
+
retainedUnknownPath?: string;
|
|
300
319
|
};
|
|
301
320
|
|
|
302
321
|
/** Default OS-close dispatcher: a direct `fs.closeSync`. */
|
|
@@ -306,7 +325,22 @@ const defaultCloseAdapter: SessionStorageWriterCloseAdapter = {
|
|
|
306
325
|
},
|
|
307
326
|
};
|
|
308
327
|
|
|
309
|
-
type NativeExactUnlinkResult =
|
|
328
|
+
type NativeExactUnlinkResult =
|
|
329
|
+
| {
|
|
330
|
+
ok: true;
|
|
331
|
+
detachedPath?: string;
|
|
332
|
+
retainedSuccessorPath?: string;
|
|
333
|
+
retainedPlaceholderPath?: string;
|
|
334
|
+
retainedUnknownPath?: string;
|
|
335
|
+
}
|
|
336
|
+
| {
|
|
337
|
+
ok: false;
|
|
338
|
+
code: string;
|
|
339
|
+
detachedPath?: string;
|
|
340
|
+
retainedSuccessorPath?: string;
|
|
341
|
+
retainedPlaceholderPath?: string;
|
|
342
|
+
retainedUnknownPath?: string;
|
|
343
|
+
};
|
|
310
344
|
type NativeExactUnlink = (
|
|
311
345
|
path: string,
|
|
312
346
|
identity: {
|
|
@@ -766,7 +800,13 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
766
800
|
expectedArtifactsIdentity,
|
|
767
801
|
expectedArtifactsTree,
|
|
768
802
|
detachedArtifactsPath,
|
|
803
|
+
retainedArtifactsSuccessorPath,
|
|
804
|
+
retainedArtifactsPlaceholderPath,
|
|
805
|
+
retainedArtifactsUnknownPath,
|
|
769
806
|
detachedTranscriptPath,
|
|
807
|
+
retainedTranscriptSuccessorPath,
|
|
808
|
+
retainedTranscriptPlaceholderPath,
|
|
809
|
+
retainedTranscriptUnknownPath,
|
|
770
810
|
plannedArtifactsPath,
|
|
771
811
|
plannedTranscriptPath,
|
|
772
812
|
artifactsRemoved,
|
|
@@ -847,7 +887,8 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
847
887
|
if (
|
|
848
888
|
!expectedArtifactsIdentity ||
|
|
849
889
|
path.dirname(detachedArtifactsPath) !== path.dirname(transcriptPath) ||
|
|
850
|
-
!path.basename(detachedArtifactsPath).startsWith(".gjc-delete-")
|
|
890
|
+
(!path.basename(detachedArtifactsPath).startsWith(".gjc-delete-") &&
|
|
891
|
+
!detachedArtifactsPath.endsWith(".removing"))
|
|
851
892
|
) {
|
|
852
893
|
throw new SessionDeleteVerificationError("artifacts", "Detached artifact cleanup evidence is invalid");
|
|
853
894
|
}
|
|
@@ -882,17 +923,20 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
882
923
|
artifactsIdentity: expectedArtifactsIdentity,
|
|
883
924
|
detachedArtifactsPath: retainedRoot,
|
|
884
925
|
artifactsTree: expectedArtifactsTree,
|
|
926
|
+
...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
|
|
927
|
+
? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
|
|
928
|
+
: {}),
|
|
929
|
+
...((removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath)
|
|
930
|
+
? { retainedPlaceholderPath: removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath }
|
|
931
|
+
: {}),
|
|
932
|
+
...((removal.retainedUnknownPath ?? retainedArtifactsUnknownPath)
|
|
933
|
+
? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
|
|
934
|
+
: {}),
|
|
885
935
|
transcriptIdentity,
|
|
886
936
|
};
|
|
887
937
|
}
|
|
888
938
|
}
|
|
889
939
|
|
|
890
|
-
if (artifactsRemoved && (detachedArtifactsPath || expectedArtifactsIdentity)) {
|
|
891
|
-
throw new SessionDeleteVerificationError(
|
|
892
|
-
"artifacts",
|
|
893
|
-
"Artifact phase receipt conflicts with pending artifact cleanup",
|
|
894
|
-
);
|
|
895
|
-
}
|
|
896
940
|
const artifactsDir = transcriptPath.slice(0, -6);
|
|
897
941
|
const artifactsIdentity = this.#optionalDirectoryIdentity(artifactsDir);
|
|
898
942
|
if (artifactsRemoved && artifactsIdentity) {
|
|
@@ -947,12 +991,48 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
947
991
|
directory: true,
|
|
948
992
|
quarantineName: path.basename(plannedArtifactsPath),
|
|
949
993
|
});
|
|
950
|
-
if (!detach.
|
|
994
|
+
if (!detach.detachedPath) {
|
|
951
995
|
throw new SessionDeleteVerificationError(
|
|
952
996
|
"artifacts",
|
|
953
997
|
`Exact artifact detach rejected: ${detach.ok ? "missing_path" : detach.code}`,
|
|
954
998
|
);
|
|
955
999
|
}
|
|
1000
|
+
if (!detach.ok && process.platform !== "win32") {
|
|
1001
|
+
let descriptor: number | undefined;
|
|
1002
|
+
try {
|
|
1003
|
+
descriptor = fs.openSync(
|
|
1004
|
+
path.dirname(transcriptPath),
|
|
1005
|
+
fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW,
|
|
1006
|
+
);
|
|
1007
|
+
const durableParent = fs.fstatSync(descriptor, { bigint: true });
|
|
1008
|
+
if (
|
|
1009
|
+
!durableParent.isDirectory() ||
|
|
1010
|
+
durableParent.dev !== parentIdentity.dev ||
|
|
1011
|
+
durableParent.ino !== parentIdentity.ino
|
|
1012
|
+
)
|
|
1013
|
+
throw new Error("parent_changed");
|
|
1014
|
+
fs.fsyncSync(descriptor);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
throw new SessionDeleteVerificationError("artifacts", "durability_failed", { cause: toError(error) });
|
|
1017
|
+
} finally {
|
|
1018
|
+
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (!detach.ok) {
|
|
1022
|
+
return {
|
|
1023
|
+
kind: "cleanup_pending",
|
|
1024
|
+
phase: "artifacts",
|
|
1025
|
+
error: new SessionDeleteVerificationError("artifacts", `Exact artifact detach retained: ${detach.code}`),
|
|
1026
|
+
artifactsIdentity,
|
|
1027
|
+
detachedArtifactsPath: detach.detachedPath,
|
|
1028
|
+
artifactsTree,
|
|
1029
|
+
...(detach.retainedSuccessorPath ? { retainedSuccessorPath: detach.retainedSuccessorPath } : {}),
|
|
1030
|
+
...(detach.retainedPlaceholderPath ? { retainedPlaceholderPath: detach.retainedPlaceholderPath } : {}),
|
|
1031
|
+
...(detach.retainedUnknownPath ? { retainedUnknownPath: detach.retainedUnknownPath } : {}),
|
|
1032
|
+
|
|
1033
|
+
transcriptIdentity,
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
956
1036
|
const removal = removeDirectoryTreeExact(detach.detachedPath, artifactsTree);
|
|
957
1037
|
if (!removal.ok) {
|
|
958
1038
|
const retainedRoot = removal.detachedPath ?? detach.detachedPath;
|
|
@@ -971,6 +1051,15 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
971
1051
|
artifactsIdentity,
|
|
972
1052
|
detachedArtifactsPath: retainedRoot,
|
|
973
1053
|
artifactsTree,
|
|
1054
|
+
...((removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath)
|
|
1055
|
+
? { retainedSuccessorPath: removal.retainedSuccessorPath ?? retainedArtifactsSuccessorPath }
|
|
1056
|
+
: {}),
|
|
1057
|
+
...((removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath)
|
|
1058
|
+
? { retainedPlaceholderPath: removal.retainedPlaceholderPath ?? retainedArtifactsPlaceholderPath }
|
|
1059
|
+
: {}),
|
|
1060
|
+
...((removal.retainedUnknownPath ?? retainedArtifactsUnknownPath)
|
|
1061
|
+
? { retainedUnknownPath: removal.retainedUnknownPath ?? retainedArtifactsUnknownPath }
|
|
1062
|
+
: {}),
|
|
974
1063
|
transcriptIdentity,
|
|
975
1064
|
};
|
|
976
1065
|
}
|
|
@@ -1010,13 +1099,27 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1010
1099
|
});
|
|
1011
1100
|
if (!deletion.ok) {
|
|
1012
1101
|
const error = exactUnlinkFailure(deletion);
|
|
1013
|
-
|
|
1102
|
+
const retainedAuthority =
|
|
1103
|
+
deletion.detachedPath ||
|
|
1104
|
+
deletion.retainedSuccessorPath ||
|
|
1105
|
+
deletion.retainedPlaceholderPath ||
|
|
1106
|
+
deletion.retainedUnknownPath;
|
|
1107
|
+
if ((error.kind === "identity" || error.kind === "symlink") && !retainedAuthority) throw error;
|
|
1014
1108
|
return {
|
|
1015
1109
|
kind: "cleanup_pending",
|
|
1016
1110
|
phase: "transcript",
|
|
1017
1111
|
error,
|
|
1018
1112
|
transcriptIdentity,
|
|
1019
1113
|
detachedTranscriptPath: deletion.detachedPath ?? detachedTranscriptPath,
|
|
1114
|
+
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
1115
|
+
? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
|
|
1116
|
+
: {}),
|
|
1117
|
+
...((deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath)
|
|
1118
|
+
? { retainedPlaceholderPath: deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath }
|
|
1119
|
+
: {}),
|
|
1120
|
+
...((deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath)
|
|
1121
|
+
? { retainedUnknownPath: deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath }
|
|
1122
|
+
: {}),
|
|
1020
1123
|
};
|
|
1021
1124
|
}
|
|
1022
1125
|
return { kind: "deleted" };
|
|
@@ -1058,13 +1161,27 @@ export class FileSessionStorage implements SessionStorage {
|
|
|
1058
1161
|
});
|
|
1059
1162
|
if (!deletion.ok) {
|
|
1060
1163
|
const error = exactUnlinkFailure(deletion);
|
|
1061
|
-
|
|
1164
|
+
const retainedAuthority =
|
|
1165
|
+
deletion.detachedPath ||
|
|
1166
|
+
deletion.retainedSuccessorPath ||
|
|
1167
|
+
deletion.retainedPlaceholderPath ||
|
|
1168
|
+
deletion.retainedUnknownPath;
|
|
1169
|
+
if ((error.kind === "identity" || error.kind === "symlink") && !retainedAuthority) throw error;
|
|
1062
1170
|
return {
|
|
1063
1171
|
kind: "cleanup_pending",
|
|
1064
1172
|
phase: "transcript",
|
|
1065
1173
|
error,
|
|
1066
1174
|
transcriptIdentity,
|
|
1067
1175
|
detachedTranscriptPath: deletion.detachedPath,
|
|
1176
|
+
...((deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath)
|
|
1177
|
+
? { retainedSuccessorPath: deletion.retainedSuccessorPath ?? retainedTranscriptSuccessorPath }
|
|
1178
|
+
: {}),
|
|
1179
|
+
...((deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath)
|
|
1180
|
+
? { retainedPlaceholderPath: deletion.retainedPlaceholderPath ?? retainedTranscriptPlaceholderPath }
|
|
1181
|
+
: {}),
|
|
1182
|
+
...((deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath)
|
|
1183
|
+
? { retainedUnknownPath: deletion.retainedUnknownPath ?? retainedTranscriptUnknownPath }
|
|
1184
|
+
: {}),
|
|
1068
1185
|
};
|
|
1069
1186
|
}
|
|
1070
1187
|
return { kind: "deleted" };
|
|
@@ -391,7 +391,7 @@ export function formatCredentialSummary(credential: ImportableCredential): strin
|
|
|
391
391
|
const identityPart = identity ? ` ${identity}` : "";
|
|
392
392
|
let expiry = "";
|
|
393
393
|
if (credential.kind === "oauth" && credential.expiresAt !== undefined) {
|
|
394
|
-
expiry = credential.expiresAt
|
|
394
|
+
expiry = credential.expiresAt <= Date.now() ? " [expired]" : "";
|
|
395
395
|
}
|
|
396
396
|
return `${provider} · ${kind}${identityPart} · token ${credential.redactedToken}${expiry} (from ${credential.source})`;
|
|
397
397
|
}
|
|
@@ -413,7 +413,10 @@ export function formatDiscoverySummary(result: CredentialDiscoveryResult): strin
|
|
|
413
413
|
return lines;
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
-
|
|
416
|
+
/** Reason shown when automatic OAuth import rejects an unusable discovery record. */
|
|
417
|
+
export const INVALID_AUTO_IMPORT_OAUTH_EXPIRY_REASON = "OAuth credential has no valid future expiry";
|
|
418
|
+
|
|
419
|
+
function hasAutoImportOAuthProviderOrigin(credential: ImportableCredential): boolean {
|
|
417
420
|
return (
|
|
418
421
|
AUTO_IMPORT_OAUTH_PROVIDER_ORIGINS[credential.provider]?.has(credential.origin) === true &&
|
|
419
422
|
credential.kind === "oauth" &&
|
|
@@ -421,8 +424,30 @@ export function isAutoImportOAuthCredential(credential: ImportableCredential): b
|
|
|
421
424
|
);
|
|
422
425
|
}
|
|
423
426
|
|
|
427
|
+
export function isAutoImportOAuthCredential(credential: ImportableCredential, now: number = Date.now()): boolean {
|
|
428
|
+
return (
|
|
429
|
+
hasAutoImportOAuthProviderOrigin(credential) &&
|
|
430
|
+
typeof credential.expiresAt === "number" &&
|
|
431
|
+
Number.isFinite(credential.expiresAt) &&
|
|
432
|
+
credential.expiresAt > now
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/** Keep rejected OAuth discoveries visible to operators without offering them for automatic import. */
|
|
437
|
+
export function getAutoImportOAuthCredentialSkips(
|
|
438
|
+
credentials: readonly ImportableCredential[],
|
|
439
|
+
now: number = Date.now(),
|
|
440
|
+
): SkippedCredential[] {
|
|
441
|
+
return credentials.flatMap(credential =>
|
|
442
|
+
hasAutoImportOAuthProviderOrigin(credential) && !isAutoImportOAuthCredential(credential, now)
|
|
443
|
+
? [{ origin: credential.origin, source: credential.source, reason: INVALID_AUTO_IMPORT_OAUTH_EXPIRY_REASON }]
|
|
444
|
+
: [],
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
424
448
|
export function filterAutoImportOAuthCredentials(credentials: readonly ImportableCredential[]): ImportableCredential[] {
|
|
425
|
-
|
|
449
|
+
const now = Date.now();
|
|
450
|
+
return credentials.filter(credential => isAutoImportOAuthCredential(credential, now));
|
|
426
451
|
}
|
|
427
452
|
|
|
428
453
|
/**
|
|
@@ -49,6 +49,7 @@ interface ProviderPreset {
|
|
|
49
49
|
baseUrl: string;
|
|
50
50
|
apiKeyEnv: string;
|
|
51
51
|
models: readonly string[];
|
|
52
|
+
modelApi?: Readonly<Record<string, ProviderSetupApi>>;
|
|
52
53
|
compat?: ProviderCompatConfig;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -113,6 +114,7 @@ function resolvePresetInput(input: ProviderSetupInput): {
|
|
|
113
114
|
apiKey?: string;
|
|
114
115
|
apiKeyEnv?: string;
|
|
115
116
|
models: readonly string[];
|
|
117
|
+
modelApi?: Readonly<Record<string, ProviderSetupApi>>;
|
|
116
118
|
api: ProviderSetupApi;
|
|
117
119
|
compat?: ProviderCompatConfig;
|
|
118
120
|
} {
|
|
@@ -152,11 +154,32 @@ function resolvePresetInput(input: ProviderSetupInput): {
|
|
|
152
154
|
apiKey: input.apiKey,
|
|
153
155
|
apiKeyEnv: input.apiKeyEnv ?? preset?.apiKeyEnv,
|
|
154
156
|
models: input.models && input.models.length > 0 ? input.models : (preset?.models ?? []),
|
|
157
|
+
modelApi: preset?.modelApi,
|
|
155
158
|
api: preset?.api ?? apiForCompatibility(compatibility),
|
|
156
159
|
compat: preset?.compat,
|
|
157
160
|
};
|
|
158
161
|
}
|
|
159
162
|
|
|
163
|
+
export function validateModelApi(
|
|
164
|
+
modelApi: Readonly<Record<string, string>> | undefined,
|
|
165
|
+
models: readonly string[],
|
|
166
|
+
presetId: string,
|
|
167
|
+
): void {
|
|
168
|
+
if (!modelApi) return;
|
|
169
|
+
const modelSet = new Set(models);
|
|
170
|
+
const validApis: readonly string[] = ["openai-responses", "openai-completions", "anthropic-messages"];
|
|
171
|
+
for (const [key, value] of Object.entries(modelApi)) {
|
|
172
|
+
if (!modelSet.has(key)) {
|
|
173
|
+
throw new Error(`Provider preset '${presetId}' declares modelApi for unknown model '${key}'.`);
|
|
174
|
+
}
|
|
175
|
+
if (!validApis.includes(value)) {
|
|
176
|
+
throw new Error(
|
|
177
|
+
`Provider preset '${presetId}' declares invalid modelApi value '${value}' for model '${key}'.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
160
183
|
function validateSetupInput(input: ProviderSetupInput): {
|
|
161
184
|
providerId: string;
|
|
162
185
|
baseUrl: string;
|
|
@@ -166,6 +189,7 @@ function validateSetupInput(input: ProviderSetupInput): {
|
|
|
166
189
|
compatibility: ProviderCompatibility;
|
|
167
190
|
api: ProviderSetupApi;
|
|
168
191
|
compat?: ProviderCompatConfig;
|
|
192
|
+
modelApi?: Readonly<Record<string, ProviderSetupApi>>;
|
|
169
193
|
preset?: ProviderPreset;
|
|
170
194
|
} {
|
|
171
195
|
const resolved = resolvePresetInput(input);
|
|
@@ -201,6 +225,7 @@ function validateSetupInput(input: ProviderSetupInput): {
|
|
|
201
225
|
|
|
202
226
|
const models = parseModelList(resolved.models);
|
|
203
227
|
if (models.length === 0) throw new Error("At least one model id is required.");
|
|
228
|
+
validateModelApi(resolved.modelApi, models, resolved.preset?.id ?? resolved.providerId);
|
|
204
229
|
|
|
205
230
|
return {
|
|
206
231
|
providerId,
|
|
@@ -210,6 +235,7 @@ function validateSetupInput(input: ProviderSetupInput): {
|
|
|
210
235
|
models,
|
|
211
236
|
compatibility: resolved.compatibility,
|
|
212
237
|
api: resolved.api,
|
|
238
|
+
modelApi: resolved.modelApi,
|
|
213
239
|
compat: resolved.compat,
|
|
214
240
|
preset: resolved.preset,
|
|
215
241
|
};
|
|
@@ -275,7 +301,10 @@ export async function addApiCompatibleProvider(input: ProviderSetupInput): Promi
|
|
|
275
301
|
baseUrl: validated.baseUrl,
|
|
276
302
|
api: validated.api,
|
|
277
303
|
auth: "apiKey",
|
|
278
|
-
models: validated.models.map(id =>
|
|
304
|
+
models: validated.models.map(id => {
|
|
305
|
+
const api = validated.modelApi?.[id];
|
|
306
|
+
return api ? { id, api } : { id };
|
|
307
|
+
}),
|
|
279
308
|
};
|
|
280
309
|
if (validated.compat) provider.compat = validated.compat;
|
|
281
310
|
if (validated.credentialSource === "env") {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
[
|
|
2
2
|
{"id":"minimax","aliases":["minimax-code"],"name":"MiniMax Coding Plan","description":"OpenAI-compatible MiniMax Coding Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"minimax-code","baseUrl":"https://api.minimax.io/v1","apiKeyEnv":"MINIMAX_CODE_API_KEY","models":["minimax-m3"],"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
|
|
3
3
|
{"id":"minimax-cn","aliases":["minimax-code-cn","minimaxi"],"name":"MiniMax Coding Plan (China)","description":"OpenAI-compatible MiniMax China endpoint","compatibility":"openai","api":"openai-completions","providerId":"minimax-code-cn","baseUrl":"https://api.minimaxi.com/v1","apiKeyEnv":"MINIMAX_CODE_CN_API_KEY","models":["minimax-m3"],"compat":{"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"reasoningContentField":"reasoning_content"}},
|
|
4
|
-
{"id":"glm","aliases":["zai","z-ai","bigmodel"],"name":"GLM / zAI","description":"OpenAI-compatible GLM endpoint from zAI/BigModel","compatibility":"openai","api":"openai-completions","providerId":"glm-proxy","baseUrl":"https://api.z.ai/api/paas/v4","apiKeyEnv":"ZAI_API_KEY","models":["glm-4.6"],"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":false,"thinkingFormat":"zai","reasoningContentField":"reasoning_content"}}
|
|
4
|
+
{"id":"glm","aliases":["zai","z-ai","bigmodel"],"name":"GLM / zAI","description":"OpenAI-compatible GLM endpoint from zAI/BigModel","compatibility":"openai","api":"openai-completions","providerId":"glm-proxy","baseUrl":"https://api.z.ai/api/paas/v4","apiKeyEnv":"ZAI_API_KEY","models":["glm-4.6"],"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":false,"thinkingFormat":"zai","reasoningContentField":"reasoning_content"}},
|
|
5
|
+
{"id":"alibaba-token-plan","aliases":["alibaba","token-plan"],"name":"Alibaba Token Plan","description":"OpenAI-compatible Alibaba Token Plan endpoint","compatibility":"openai","api":"openai-completions","providerId":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","apiKeyEnv":"ALIBABA_TOKEN_PLAN_API_KEY","models":["qwen3.8-max-preview","glm-5.2","deepseek-v4-pro"],"modelApi":{"qwen3.8-max-preview":"openai-responses","glm-5.2":"openai-completions","deepseek-v4-pro":"openai-completions"},"compat":{"supportsDeveloperRole":false}}
|
|
5
6
|
]
|
|
@@ -1515,7 +1515,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
1515
1515
|
},
|
|
1516
1516
|
{
|
|
1517
1517
|
name: "btw",
|
|
1518
|
-
description: "
|
|
1518
|
+
description: "Start an ephemeral multi-turn side chat using the current session context",
|
|
1519
1519
|
inlineHint: "<question>",
|
|
1520
1520
|
allowArgs: true,
|
|
1521
1521
|
handleTui: async (command, runtime) => {
|
package/src/ssh/utils.ts
CHANGED
|
@@ -3,6 +3,20 @@ export function sanitizeHostName(name: string): string {
|
|
|
3
3
|
return sanitized.length > 0 ? sanitized : "remote";
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
export function validateSshDestination(username: string | undefined, host: string): string | undefined {
|
|
7
|
+
if (host.startsWith("-") || username?.startsWith("-")) {
|
|
8
|
+
return "username and host must not begin with '-'";
|
|
9
|
+
}
|
|
10
|
+
if (/\0|\r|\n/.test(host) || (username !== undefined && /\0|\r|\n/.test(username))) {
|
|
11
|
+
return "username and host must not contain NUL or line breaks";
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
export function buildSshTarget(username: string | undefined, host: string): string {
|
|
17
|
+
const validationError = validateSshDestination(username, host);
|
|
18
|
+
if (validationError) {
|
|
19
|
+
throw new Error(`Invalid SSH destination: ${validationError}`);
|
|
20
|
+
}
|
|
7
21
|
return username ? `${username}@${host}` : host;
|
|
8
22
|
}
|
package/src/task/discovery.ts
CHANGED
|
@@ -14,6 +14,7 @@ import * as path from "node:path";
|
|
|
14
14
|
import { logger } from "@gajae-code/utils";
|
|
15
15
|
import { isProviderEnabled } from "../capability";
|
|
16
16
|
import { findAllNearestProjectConfigDirs, getConfigDirs } from "../config";
|
|
17
|
+
import type { Settings } from "../config/settings";
|
|
17
18
|
import { listClaudePluginRoots } from "../discovery/helpers";
|
|
18
19
|
import { rootContainsGjcManifest } from "../extensibility/gjc-plugins/paths";
|
|
19
20
|
import { loadBundledAgents, parseAgent } from "./agents";
|
|
@@ -57,8 +58,13 @@ async function loadAgentsFromDir(dir: string, source: AgentSource): Promise<Agen
|
|
|
57
58
|
* Precedence (highest wins): .gjc project, .gjc user, GJC plugins, then bundled
|
|
58
59
|
*
|
|
59
60
|
* @param cwd - Current working directory for project agent discovery
|
|
61
|
+
* @param activeSettings - Settings used to resolve enabled providers.
|
|
60
62
|
*/
|
|
61
|
-
export async function discoverAgents(
|
|
63
|
+
export async function discoverAgents(
|
|
64
|
+
cwd: string,
|
|
65
|
+
home: string = os.homedir(),
|
|
66
|
+
activeSettings?: Settings,
|
|
67
|
+
): Promise<DiscoveryResult> {
|
|
62
68
|
const resolvedCwd = path.resolve(cwd);
|
|
63
69
|
const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map(entry => entry.source)));
|
|
64
70
|
|
|
@@ -91,7 +97,7 @@ export async function discoverAgents(cwd: string, home: string = os.homedir()):
|
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
// Load agents from GJC marketplace plugins.
|
|
94
|
-
const { roots: pluginRoots } = isProviderEnabled("claude-plugins")
|
|
100
|
+
const { roots: pluginRoots } = isProviderEnabled("claude-plugins", activeSettings)
|
|
95
101
|
? await listClaudePluginRoots(home, resolvedCwd)
|
|
96
102
|
: { roots: [] };
|
|
97
103
|
const nonGjcPluginRoots = [];
|
package/src/task/gjc-command.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import process from "node:process";
|
|
2
2
|
|
|
3
|
-
import { $
|
|
3
|
+
import { $pickenv } from "@gajae-code/utils";
|
|
4
4
|
|
|
5
5
|
interface GjcCommand {
|
|
6
6
|
cmd: string;
|
|
@@ -12,7 +12,7 @@ const DEFAULT_CMD = process.platform === "win32" ? "gjc.cmd" : "gjc";
|
|
|
12
12
|
const DEFAULT_SHELL = process.platform === "win32";
|
|
13
13
|
|
|
14
14
|
export function resolveGjcCommand(): GjcCommand {
|
|
15
|
-
const envCmd = $
|
|
15
|
+
const envCmd = $pickenv("GJC_SUBPROCESS_CMD", "PI_SUBPROCESS_CMD");
|
|
16
16
|
if (envCmd?.trim()) {
|
|
17
17
|
return { cmd: envCmd, args: [], shell: DEFAULT_SHELL };
|
|
18
18
|
}
|