@gajae-code/coding-agent 0.6.4 → 0.7.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 +51 -0
- package/dist/types/async/job-manager.d.ts +3 -1
- package/dist/types/cli/daemon-cli.d.ts +25 -0
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/cli/notify-cli.d.ts +23 -0
- package/dist/types/cli/setup-cli.d.ts +20 -1
- package/dist/types/commands/daemon.d.ts +41 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/commands/notify.d.ts +41 -0
- package/dist/types/config/keybindings.d.ts +4 -0
- package/dist/types/config/model-profile-activation.d.ts +12 -0
- package/dist/types/config/model-profiles.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +38 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +57 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -2
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +1 -1
- package/dist/types/gjc-runtime/session-layout.d.ts +59 -0
- package/dist/types/gjc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +1 -1
- package/dist/types/gjc-runtime/state-runtime.d.ts +5 -4
- package/dist/types/gjc-runtime/state-schema.d.ts +2 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +38 -7
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +15 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +21 -4
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +1 -1
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +1 -1
- package/dist/types/harness-control-plane/storage.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +12 -4
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/oauth-selector.d.ts +2 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -2
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +1 -1
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/notifications/config-commands.d.ts +26 -0
- package/dist/types/notifications/config.d.ts +61 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +62 -0
- package/dist/types/notifications/index.d.ts +28 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +93 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +11 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +56 -0
- package/dist/types/notifications/telegram-daemon.d.ts +276 -0
- package/dist/types/notifications/telegram-reference.d.ts +111 -0
- package/dist/types/notifications/threaded-inbound.d.ts +58 -0
- package/dist/types/notifications/threaded-render.d.ts +66 -0
- package/dist/types/notifications/topic-registry.d.ts +67 -0
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +1 -1
- package/dist/types/rlm/index.d.ts +12 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +26 -0
- package/dist/types/session/agent-session.d.ts +39 -2
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +3 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/skill-state/active-state.d.ts +6 -11
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/workflow-hud.d.ts +2 -0
- package/dist/types/task/spawn-gate.d.ts +1 -10
- package/dist/types/tools/ask-answer-registry.d.ts +13 -0
- package/dist/types/tools/index.d.ts +18 -0
- package/dist/types/tools/subagent.d.ts +3 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +3 -0
- package/src/async/job-manager.ts +5 -1
- package/src/cli/daemon-cli.ts +122 -0
- package/src/cli/migrate-cli.ts +106 -0
- package/src/cli/notify-cli.ts +274 -0
- package/src/cli/setup-cli.ts +173 -84
- package/src/cli.ts +3 -0
- package/src/commands/daemon.ts +47 -0
- package/src/commands/deep-interview.ts +2 -2
- package/src/commands/migrate.ts +46 -0
- package/src/commands/notify.ts +61 -0
- package/src/commands/setup.ts +11 -1
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +7 -3
- package/src/config/model-profile-activation.ts +74 -5
- package/src/config/model-profiles.ts +7 -4
- package/src/config/model-registry.ts +6 -3
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +29 -0
- package/src/coordinator/contract.ts +3 -0
- package/src/coordinator-mcp/policy.ts +10 -2
- package/src/coordinator-mcp/server.ts +270 -1
- package/src/daemon/builtin.ts +46 -0
- package/src/daemon/control-types.ts +65 -0
- package/src/daemon/runtime.ts +51 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +0 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +28 -24
- package/src/defaults/gjc/skills/ralplan/SKILL.md +8 -4
- package/src/defaults/gjc/skills/team/SKILL.md +51 -47
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +33 -13
- package/src/extensibility/custom-commands/loader.ts +0 -7
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +8 -0
- package/src/extensibility/gjc-plugins/injection.ts +23 -4
- package/src/extensibility/gjc-plugins/state.ts +16 -1
- package/src/gjc-runtime/deep-interview-recorder.ts +51 -18
- package/src/gjc-runtime/deep-interview-runtime.ts +49 -23
- package/src/gjc-runtime/goal-mode-request.ts +26 -11
- package/src/gjc-runtime/launch-tmux.ts +6 -1
- package/src/gjc-runtime/ralplan-runtime.ts +79 -50
- package/src/gjc-runtime/session-layout.ts +180 -0
- package/src/gjc-runtime/session-resolution.ts +217 -0
- package/src/gjc-runtime/state-graph.ts +1 -2
- package/src/gjc-runtime/state-migrations.ts +1 -0
- package/src/gjc-runtime/state-runtime.ts +247 -124
- package/src/gjc-runtime/state-schema.ts +2 -0
- package/src/gjc-runtime/state-writer.ts +289 -41
- package/src/gjc-runtime/team-runtime.ts +43 -19
- package/src/gjc-runtime/tmux-sessions.ts +7 -1
- package/src/gjc-runtime/ultragoal-guard.ts +102 -4
- package/src/gjc-runtime/ultragoal-runtime.ts +226 -60
- package/src/gjc-runtime/workflow-command-ref.ts +1 -2
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -2
- package/src/gjc-runtime/workflow-manifest.ts +12 -3
- package/src/goals/tools/goal-tool.ts +11 -2
- package/src/harness-control-plane/storage.ts +14 -4
- package/src/hooks/native-skill-hook.ts +38 -12
- package/src/hooks/skill-state.ts +178 -83
- package/src/internal-urls/docs-index.generated.ts +9 -6
- package/src/main.ts +30 -0
- package/src/migrate/action-planner.ts +318 -0
- package/src/migrate/adapters/claude-code.ts +39 -0
- package/src/migrate/adapters/codex.ts +70 -0
- package/src/migrate/adapters/index.ts +277 -0
- package/src/migrate/adapters/opencode.ts +52 -0
- package/src/migrate/executor.ts +81 -0
- package/src/migrate/mcp-mapper.ts +152 -0
- package/src/migrate/report.ts +104 -0
- package/src/migrate/skill-normalizer.ts +80 -0
- package/src/migrate/types.ts +163 -0
- package/src/modes/acp/acp-event-mapper.ts +1 -0
- package/src/modes/bridge/bridge-mode.ts +2 -2
- package/src/modes/components/custom-editor.ts +30 -20
- package/src/modes/components/hook-editor.ts +7 -2
- package/src/modes/components/oauth-selector.ts +19 -0
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/selector-controller.ts +80 -17
- package/src/modes/interactive-mode.ts +6 -2
- package/src/modes/rpc/rpc-mode.ts +2 -2
- package/src/modes/runtime-init.ts +1 -0
- package/src/modes/shared/agent-wire/event-contract.ts +1 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +1 -0
- package/src/modes/shared/agent-wire/event-observation.ts +16 -0
- package/src/modes/shared/agent-wire/unattended-audit.ts +3 -2
- package/src/modes/shared/agent-wire/unattended-session.ts +22 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/ui-helpers.ts +23 -0
- package/src/notifications/config-commands.ts +50 -0
- package/src/notifications/config.ts +107 -0
- package/src/notifications/helpers.ts +135 -0
- package/src/notifications/html-format.ts +389 -0
- package/src/notifications/index.ts +663 -0
- package/src/notifications/rate-limit-pool.ts +179 -0
- package/src/notifications/telegram-cli.ts +194 -0
- package/src/notifications/telegram-daemon-cli.ts +74 -0
- package/src/notifications/telegram-daemon-control.ts +370 -0
- package/src/notifications/telegram-daemon.ts +1370 -0
- package/src/notifications/telegram-reference.ts +335 -0
- package/src/notifications/threaded-inbound.ts +80 -0
- package/src/notifications/threaded-render.ts +155 -0
- package/src/notifications/topic-registry.ts +133 -0
- package/src/prompts/agents/init.md +1 -1
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/tools/ast-grep.md +1 -1
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +1 -2
- package/src/research-plan/index.ts +1 -0
- package/src/research-plan/ledger.ts +177 -0
- package/src/rlm/artifacts.ts +12 -3
- package/src/rlm/index.ts +26 -0
- package/src/runtime-mcp/config-writer.ts +46 -0
- package/src/sdk.ts +16 -0
- package/src/session/agent-session.ts +128 -24
- package/src/session/auth-storage.ts +3 -0
- package/src/session/session-dump-format.ts +43 -2
- package/src/session/session-manager.ts +39 -5
- package/src/setup/credential-auto-import.ts +258 -0
- package/src/setup/credential-import.ts +17 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +10 -0
- package/src/setup/hermes-setup.ts +1 -1
- package/src/setup/host-plugin-setup.ts +142 -0
- package/src/skill-state/active-state.ts +72 -108
- package/src/skill-state/canonical-skills.ts +4 -0
- package/src/skill-state/deep-interview-mutation-guard.ts +28 -109
- package/src/skill-state/workflow-hud.ts +4 -2
- package/src/skill-state/workflow-state-contract.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +4 -1
- package/src/task/agents.ts +1 -22
- package/src/task/executor.ts +5 -1
- package/src/task/index.ts +1 -41
- package/src/task/spawn-gate.ts +1 -38
- package/src/task/types.ts +1 -1
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +108 -16
- package/src/tools/computer.ts +58 -4
- package/src/tools/image-gen.ts +5 -8
- package/src/tools/index.ts +19 -0
- package/src/tools/inspect-image.ts +16 -11
- package/src/tools/subagent-render.ts +7 -0
- package/src/tools/subagent.ts +38 -7
- package/dist/types/extensibility/custom-commands/bundled/review/index.d.ts +0 -10
- package/src/extensibility/custom-commands/bundled/review/index.ts +0 -456
- package/src/prompts/agents/explore.md +0 -58
- package/src/prompts/agents/plan.md +0 -49
- package/src/prompts/agents/reviewer.md +0 -141
- package/src/prompts/agents/task.md +0 -16
- package/src/prompts/review-request.md +0 -70
|
@@ -47,13 +47,54 @@ function stripTypeBoxFields(obj: unknown): unknown {
|
|
|
47
47
|
return obj;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
function escapeXmlAttribute(input: string): string {
|
|
51
|
+
return input.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function escapeXmlText(input: string): string {
|
|
55
|
+
return input.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function decodeCodePoint(hex: string): string {
|
|
59
|
+
const codePoint = Number.parseInt(hex, 16);
|
|
60
|
+
if (
|
|
61
|
+
!Number.isFinite(codePoint) ||
|
|
62
|
+
codePoint < 0x20 ||
|
|
63
|
+
(codePoint >= 0x7f && codePoint <= 0x9f) ||
|
|
64
|
+
(codePoint >= 0xd800 && codePoint <= 0xdfff)
|
|
65
|
+
) {
|
|
66
|
+
return `\\u${hex}`;
|
|
67
|
+
}
|
|
68
|
+
return String.fromCharCode(codePoint);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function decodeUnicodeEscapeText(input: string): string {
|
|
72
|
+
return input
|
|
73
|
+
.replace(/\\\\u([0-9a-fA-F]{4})/g, (_match, hex: string) => decodeCodePoint(hex))
|
|
74
|
+
.replace(/\\u([0-9a-fA-F]{4})/g, (_match, hex: string) => decodeCodePoint(hex));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatParameterValue(value: unknown): string {
|
|
78
|
+
const raw = typeof value === "string" ? value : (JSON.stringify(value, null, "\t") ?? "null");
|
|
79
|
+
return escapeXmlText(decodeUnicodeEscapeText(raw));
|
|
80
|
+
}
|
|
81
|
+
|
|
50
82
|
/** Serialize an object as XML parameter elements, one per key. */
|
|
51
83
|
function formatArgsAsXml(args: Record<string, unknown>, indent = "\t"): string {
|
|
52
84
|
const parts: string[] = [];
|
|
53
85
|
for (const [key, value] of Object.entries(args)) {
|
|
54
86
|
if (key === INTENT_FIELD) continue;
|
|
55
|
-
const
|
|
56
|
-
|
|
87
|
+
const escapedKey = escapeXmlAttribute(key);
|
|
88
|
+
const text = formatParameterValue(value);
|
|
89
|
+
if (text.includes("\n")) {
|
|
90
|
+
const indentedText = text
|
|
91
|
+
.split("\n")
|
|
92
|
+
.map(line => `${indent}\t${line}`)
|
|
93
|
+
.join("\n");
|
|
94
|
+
parts.push(`${indent}<parameter name="${escapedKey}">\n${indentedText}\n${indent}</parameter>`);
|
|
95
|
+
} else {
|
|
96
|
+
parts.push(`${indent}<parameter name="${escapedKey}">${text}</parameter>`);
|
|
97
|
+
}
|
|
57
98
|
}
|
|
58
99
|
return parts.join("\n");
|
|
59
100
|
}
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
toError,
|
|
29
29
|
} from "@gajae-code/utils";
|
|
30
30
|
import { writeTextAtomic } from "../gjc-runtime/state-writer";
|
|
31
|
+
import * as git from "../utils/git";
|
|
31
32
|
import { ArtifactManager } from "./artifacts";
|
|
32
33
|
import {
|
|
33
34
|
type BlobPutResult,
|
|
@@ -791,6 +792,25 @@ function writeTerminalBreadcrumb(cwd: string, sessionFile: string): void {
|
|
|
791
792
|
write.catch(() => {});
|
|
792
793
|
}
|
|
793
794
|
|
|
795
|
+
/**
|
|
796
|
+
* Two paths belong to linked worktrees of the same repository when they share a
|
|
797
|
+
* git common dir but resolve to different git dirs (i.e. one is a `git worktree`
|
|
798
|
+
* of the other). `--worktree` sessions run from such a linked worktree, so a
|
|
799
|
+
* `--continue` from the main checkout should still resolve their breadcrumb.
|
|
800
|
+
*/
|
|
801
|
+
function isLinkedWorktreePeer(a: string, b: string): boolean {
|
|
802
|
+
const ra = git.repo.resolveSync(a);
|
|
803
|
+
const rb = git.repo.resolveSync(b);
|
|
804
|
+
if (ra === null || rb === null) return false;
|
|
805
|
+
// Canonicalize: a worktree's commondir is stored as an absolute path that may
|
|
806
|
+
// differ from the main checkout only by a symlink prefix (e.g. macOS
|
|
807
|
+
// /tmp -> /private/tmp), so compare resolved-equivalent paths.
|
|
808
|
+
return (
|
|
809
|
+
resolveEquivalentPath(ra.commonDir) === resolveEquivalentPath(rb.commonDir) &&
|
|
810
|
+
resolveEquivalentPath(ra.gitDir) !== resolveEquivalentPath(rb.gitDir)
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
|
|
794
814
|
/**
|
|
795
815
|
* Read the terminal breadcrumb for the current terminal, scoped to a cwd.
|
|
796
816
|
* Returns the session file path if it exists and matches the cwd, null otherwise.
|
|
@@ -808,8 +828,12 @@ async function readTerminalBreadcrumb(cwd: string): Promise<string | null> {
|
|
|
808
828
|
const breadcrumbCwd = lines[0];
|
|
809
829
|
const sessionFile = lines[1];
|
|
810
830
|
|
|
811
|
-
//
|
|
812
|
-
|
|
831
|
+
// Honor the breadcrumb when the cwd matches, or when it points to a linked
|
|
832
|
+
// worktree of the same repository (e.g. a `--worktree` session resumed from
|
|
833
|
+
// the main checkout). A genuinely different project is still ignored.
|
|
834
|
+
if (path.resolve(breadcrumbCwd) !== path.resolve(cwd) && !isLinkedWorktreePeer(breadcrumbCwd, cwd)) {
|
|
835
|
+
return null;
|
|
836
|
+
}
|
|
813
837
|
|
|
814
838
|
// Verify the session file still exists
|
|
815
839
|
const stat = fs.statSync(sessionFile, { throwIfNoEntry: false });
|
|
@@ -4010,12 +4034,22 @@ export class SessionManager {
|
|
|
4010
4034
|
// Prefer terminal-scoped breadcrumb (handles concurrent sessions correctly)
|
|
4011
4035
|
const terminalSession = await readTerminalBreadcrumb(cwd);
|
|
4012
4036
|
const mostRecent = terminalSession ?? (await findMostRecentSession(dir, storage));
|
|
4013
|
-
const manager = new SessionManager(cwd, dir, true, storage);
|
|
4014
4037
|
if (mostRecent) {
|
|
4038
|
+
// Adopt the resumed session's recorded cwd and its own directory. A
|
|
4039
|
+
// `--worktree` session lives in a linked worktree whose path differs from
|
|
4040
|
+
// the invocation cwd; binding the manager (and HUD) to `cwd` would leave
|
|
4041
|
+
// it on the main checkout instead of the worktree it was created in.
|
|
4042
|
+
const header = (await loadEntriesFromFile(mostRecent, storage)).find(e => e.type === "session") as
|
|
4043
|
+
| SessionHeader
|
|
4044
|
+
| undefined;
|
|
4045
|
+
const resumeCwd = header?.cwd || cwd;
|
|
4046
|
+
const resumeDir = sessionDir ?? path.resolve(mostRecent, "..");
|
|
4047
|
+
const manager = new SessionManager(resumeCwd, resumeDir, true, storage);
|
|
4015
4048
|
await manager.#initSessionFile(mostRecent);
|
|
4016
|
-
|
|
4017
|
-
manager.#initNewSession();
|
|
4049
|
+
return manager;
|
|
4018
4050
|
}
|
|
4051
|
+
const manager = new SessionManager(cwd, dir, true, storage);
|
|
4052
|
+
manager.#initNewSession();
|
|
4019
4053
|
return manager;
|
|
4020
4054
|
}
|
|
4021
4055
|
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
AuthCredential,
|
|
5
|
+
AuthCredentialIfAbsentReason,
|
|
6
|
+
AuthCredentialIfAbsentSnapshotResult,
|
|
7
|
+
AuthStorage,
|
|
8
|
+
} from "@gajae-code/ai";
|
|
9
|
+
import { getAgentDir, logger, VERSION } from "@gajae-code/utils";
|
|
10
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type CredentialDiscoveryResult,
|
|
14
|
+
type CredentialOrigin,
|
|
15
|
+
type DiscoveryOptions,
|
|
16
|
+
discoverExternalCredentials,
|
|
17
|
+
EXTERNAL_PROVIDER_LABELS,
|
|
18
|
+
type ExternalProvider,
|
|
19
|
+
filterAutoImportOAuthCredentials,
|
|
20
|
+
formatCredentialSummary,
|
|
21
|
+
type ImportableCredential,
|
|
22
|
+
} from "./credential-import";
|
|
23
|
+
|
|
24
|
+
export const CREDENTIAL_AUTO_IMPORT_ROTATION_WARNING =
|
|
25
|
+
"Refreshing in gjc may log out the Claude/Codex CLI because OAuth refresh tokens can rotate.";
|
|
26
|
+
|
|
27
|
+
export type CredentialAutoImportSourceLabel = "claude-code-file" | "claude-code-keychain" | "codex-file";
|
|
28
|
+
export type CredentialAutoImportTrigger = "startup" | "bare-login" | "setup-cli";
|
|
29
|
+
|
|
30
|
+
const CREDENTIAL_AUTO_IMPORT_STATE_FILENAME = "credential-auto-import-state.json";
|
|
31
|
+
|
|
32
|
+
interface CredentialAutoImportStateFile {
|
|
33
|
+
lastImportVersion?: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getCredentialAutoImportStatePath(agentDir: string = getAgentDir()): string {
|
|
37
|
+
return path.join(agentDir, CREDENTIAL_AUTO_IMPORT_STATE_FILENAME);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function readCredentialImportMarker(agentDir?: string): Promise<string | undefined> {
|
|
41
|
+
try {
|
|
42
|
+
const raw = await fs.readFile(getCredentialAutoImportStatePath(agentDir), "utf-8");
|
|
43
|
+
const parsed = JSON.parse(raw) as CredentialAutoImportStateFile;
|
|
44
|
+
return typeof parsed.lastImportVersion === "string" ? parsed.lastImportVersion : undefined;
|
|
45
|
+
} catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function writeCredentialImportMarker(version: string, agentDir?: string): Promise<boolean> {
|
|
51
|
+
try {
|
|
52
|
+
const statePath = getCredentialAutoImportStatePath(agentDir);
|
|
53
|
+
await fs.mkdir(path.dirname(statePath), { recursive: true });
|
|
54
|
+
await fs.writeFile(statePath, `${JSON.stringify({ lastImportVersion: version })}\n`);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (error: unknown) {
|
|
57
|
+
logger.warn("Failed to persist credential auto-import state", { error });
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export enum CredentialAutoImportFailureClass {
|
|
63
|
+
DiscoveryUnavailable = "discovery-unavailable",
|
|
64
|
+
SourceUnreadable = "source-unreadable",
|
|
65
|
+
SourceMalformed = "source-malformed",
|
|
66
|
+
KeychainDenied = "keychain-denied",
|
|
67
|
+
WriteInvalid = "write-invalid",
|
|
68
|
+
WriteConflict = "write-conflict",
|
|
69
|
+
BrokerUnavailable = "broker-unavailable",
|
|
70
|
+
BrokerUnsupported = "broker-unsupported",
|
|
71
|
+
Unknown = "unknown",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface CredentialAutoImportSkipped {
|
|
75
|
+
credential: ImportableCredential;
|
|
76
|
+
reason: AuthCredentialIfAbsentReason;
|
|
77
|
+
entries: AuthCredentialIfAbsentSnapshotResult["entries"];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CredentialAutoImportFailure {
|
|
81
|
+
credential?: ImportableCredential;
|
|
82
|
+
origin?: CredentialOrigin;
|
|
83
|
+
source?: string;
|
|
84
|
+
failureClass: CredentialAutoImportFailureClass;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CredentialAutoImportResult {
|
|
88
|
+
imported: ImportableCredential[];
|
|
89
|
+
skipped: CredentialAutoImportSkipped[];
|
|
90
|
+
failures: CredentialAutoImportFailure[];
|
|
91
|
+
discovered: boolean;
|
|
92
|
+
discovery?: CredentialDiscoveryResult;
|
|
93
|
+
globalDiscoveryFailure?: CredentialAutoImportFailure;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type CredentialAutoImportAuthStorage = Pick<AuthStorage, "importCredentialIfAbsent">;
|
|
97
|
+
|
|
98
|
+
export interface CredentialAutoImportOptions {
|
|
99
|
+
authStorage: CredentialAutoImportAuthStorage;
|
|
100
|
+
discover?: (options?: DiscoveryOptions) => Promise<CredentialDiscoveryResult>;
|
|
101
|
+
discoveryOptions?: DiscoveryOptions;
|
|
102
|
+
trigger: CredentialAutoImportTrigger;
|
|
103
|
+
sourceLabel?: CredentialAutoImportSourceLabel;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function classifyDiscoverySkip(reason: string, origin: CredentialOrigin): CredentialAutoImportFailureClass {
|
|
107
|
+
const lower = reason.toLowerCase();
|
|
108
|
+
if (
|
|
109
|
+
origin === "claude-code-keychain" &&
|
|
110
|
+
(lower.includes("eacces") || lower.includes("eperm") || lower.includes("denied"))
|
|
111
|
+
) {
|
|
112
|
+
return CredentialAutoImportFailureClass.KeychainDenied;
|
|
113
|
+
}
|
|
114
|
+
if (lower.includes("malformed")) return CredentialAutoImportFailureClass.SourceMalformed;
|
|
115
|
+
if (lower.includes("unreadable")) return CredentialAutoImportFailureClass.SourceUnreadable;
|
|
116
|
+
return CredentialAutoImportFailureClass.Unknown;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function classifyWriteFailure(error: unknown): CredentialAutoImportFailureClass {
|
|
120
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
121
|
+
if (message.includes("invalid")) return CredentialAutoImportFailureClass.WriteInvalid;
|
|
122
|
+
if (message.includes("conflict") || message.includes("constraint"))
|
|
123
|
+
return CredentialAutoImportFailureClass.WriteConflict;
|
|
124
|
+
if (
|
|
125
|
+
message.includes("broker") &&
|
|
126
|
+
(message.includes("unsupported") || message.includes("404") || message.includes("501"))
|
|
127
|
+
) {
|
|
128
|
+
return CredentialAutoImportFailureClass.BrokerUnsupported;
|
|
129
|
+
}
|
|
130
|
+
if (message.includes("broker") || message.includes("fetch") || message.includes("network")) {
|
|
131
|
+
return CredentialAutoImportFailureClass.BrokerUnavailable;
|
|
132
|
+
}
|
|
133
|
+
return CredentialAutoImportFailureClass.Unknown;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function runExternalCredentialAutoImport({
|
|
137
|
+
authStorage,
|
|
138
|
+
discover = discoverExternalCredentials,
|
|
139
|
+
discoveryOptions,
|
|
140
|
+
}: CredentialAutoImportOptions): Promise<CredentialAutoImportResult> {
|
|
141
|
+
let discovery: CredentialDiscoveryResult;
|
|
142
|
+
try {
|
|
143
|
+
discovery = await discover(discoveryOptions);
|
|
144
|
+
} catch {
|
|
145
|
+
const globalDiscoveryFailure = { failureClass: CredentialAutoImportFailureClass.DiscoveryUnavailable };
|
|
146
|
+
return {
|
|
147
|
+
imported: [],
|
|
148
|
+
skipped: [],
|
|
149
|
+
failures: [globalDiscoveryFailure],
|
|
150
|
+
discovered: false,
|
|
151
|
+
globalDiscoveryFailure,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const candidates = filterAutoImportOAuthCredentials(discovery.importable);
|
|
156
|
+
const failures: CredentialAutoImportFailure[] = discovery.skipped.map(skip => ({
|
|
157
|
+
origin: skip.origin,
|
|
158
|
+
source: skip.source,
|
|
159
|
+
failureClass: classifyDiscoverySkip(skip.reason, skip.origin),
|
|
160
|
+
}));
|
|
161
|
+
const imported: ImportableCredential[] = [];
|
|
162
|
+
const skipped: CredentialAutoImportSkipped[] = [];
|
|
163
|
+
const importIfAbsent = authStorage.importCredentialIfAbsent;
|
|
164
|
+
|
|
165
|
+
for (const credential of candidates) {
|
|
166
|
+
try {
|
|
167
|
+
const outcome = await importIfAbsent.call(
|
|
168
|
+
authStorage,
|
|
169
|
+
credential.provider,
|
|
170
|
+
credential.credential as AuthCredential,
|
|
171
|
+
);
|
|
172
|
+
if (outcome.inserted === true) {
|
|
173
|
+
imported.push(credential);
|
|
174
|
+
} else {
|
|
175
|
+
skipped.push({ credential, reason: outcome.reason, entries: outcome.entries });
|
|
176
|
+
}
|
|
177
|
+
} catch (error) {
|
|
178
|
+
failures.push({ credential, failureClass: classifyWriteFailure(error) });
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { imported, skipped, failures, discovered: true, discovery };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function buildCredentialAutoImportNotice(
|
|
186
|
+
result: Pick<CredentialAutoImportResult, "imported">,
|
|
187
|
+
): string | undefined {
|
|
188
|
+
if (result.imported.length === 0) return undefined;
|
|
189
|
+
const providers = [
|
|
190
|
+
...new Set(result.imported.map(c => EXTERNAL_PROVIDER_LABELS[c.provider as ExternalProvider] ?? c.provider)),
|
|
191
|
+
];
|
|
192
|
+
const success = `Imported ${result.imported.length} external OAuth credential(s) into gjc: ${providers.join(", ")}.`;
|
|
193
|
+
return `${success}\n${CREDENTIAL_AUTO_IMPORT_ROTATION_WARNING}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function formatCredentialAutoImportResult(result: CredentialAutoImportResult): string[] {
|
|
197
|
+
const lines: string[] = [];
|
|
198
|
+
for (const credential of result.imported) lines.push(`imported ${formatCredentialSummary(credential)}`);
|
|
199
|
+
for (const skip of result.skipped) lines.push(`skipped ${skip.credential.source}: ${skip.reason}`);
|
|
200
|
+
for (const failure of result.failures) {
|
|
201
|
+
const label = failure.credential?.source ?? failure.source ?? "external credential discovery";
|
|
202
|
+
lines.push(`failed ${label}: ${failure.failureClass}`);
|
|
203
|
+
}
|
|
204
|
+
return lines;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface CredentialImportMarkerStore {
|
|
208
|
+
read: () => Promise<string | undefined> | string | undefined;
|
|
209
|
+
write: (version: string) => Promise<boolean> | boolean;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface StartupCredentialAutoImportOptions {
|
|
213
|
+
authStorage: CredentialAutoImportOptions["authStorage"];
|
|
214
|
+
modelRegistry: Pick<ModelRegistry, "refresh">;
|
|
215
|
+
discover?: CredentialAutoImportOptions["discover"];
|
|
216
|
+
version?: string;
|
|
217
|
+
agentDir?: string;
|
|
218
|
+
markerStore?: CredentialImportMarkerStore;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function runStartupCredentialAutoImportIfNeeded({
|
|
222
|
+
authStorage: activeAuthStorage,
|
|
223
|
+
modelRegistry: activeModelRegistry,
|
|
224
|
+
discover,
|
|
225
|
+
version = VERSION,
|
|
226
|
+
agentDir,
|
|
227
|
+
markerStore,
|
|
228
|
+
}: StartupCredentialAutoImportOptions): Promise<string | undefined> {
|
|
229
|
+
const store = markerStore ?? {
|
|
230
|
+
read: () => readCredentialImportMarker(agentDir),
|
|
231
|
+
write: (nextVersion: string) => writeCredentialImportMarker(nextVersion, agentDir),
|
|
232
|
+
};
|
|
233
|
+
const lastVersion = await store.read();
|
|
234
|
+
if (lastVersion === version) {
|
|
235
|
+
// Steady state: user already completed this version's auto-import gate. Skip all file/Keychain reads.
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const result = await runExternalCredentialAutoImport({
|
|
240
|
+
authStorage: activeAuthStorage,
|
|
241
|
+
discover,
|
|
242
|
+
trigger: "startup",
|
|
243
|
+
});
|
|
244
|
+
if (!result.discovered) {
|
|
245
|
+
return undefined;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const candidates = filterAutoImportOAuthCredentials(result.discovery?.importable ?? []);
|
|
249
|
+
if (candidates.length > 0 && result.imported.length === 0 && result.skipped.length === 0) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
await store.write(version);
|
|
253
|
+
|
|
254
|
+
if (result.imported.length > 0) {
|
|
255
|
+
await activeModelRegistry.refresh("offline");
|
|
256
|
+
}
|
|
257
|
+
return buildCredentialAutoImportNotice(result);
|
|
258
|
+
}
|
|
@@ -25,6 +25,11 @@ export type ExternalProvider = "anthropic" | "openai-codex";
|
|
|
25
25
|
/** Where a discovered credential came from. */
|
|
26
26
|
export type CredentialOrigin = "claude-code-file" | "claude-code-keychain" | "codex-file";
|
|
27
27
|
|
|
28
|
+
export const AUTO_IMPORT_OAUTH_PROVIDER_ORIGINS: Record<ExternalProvider, ReadonlySet<CredentialOrigin>> = {
|
|
29
|
+
anthropic: new Set<CredentialOrigin>(["claude-code-file", "claude-code-keychain"]),
|
|
30
|
+
"openai-codex": new Set<CredentialOrigin>(["codex-file"]),
|
|
31
|
+
};
|
|
32
|
+
|
|
28
33
|
/** Human labels for providers, used in redacted summaries. */
|
|
29
34
|
export const EXTERNAL_PROVIDER_LABELS: Record<ExternalProvider, string> = {
|
|
30
35
|
anthropic: "Claude (Anthropic)",
|
|
@@ -408,6 +413,18 @@ export function formatDiscoverySummary(result: CredentialDiscoveryResult): strin
|
|
|
408
413
|
return lines;
|
|
409
414
|
}
|
|
410
415
|
|
|
416
|
+
export function isAutoImportOAuthCredential(credential: ImportableCredential): boolean {
|
|
417
|
+
return (
|
|
418
|
+
AUTO_IMPORT_OAUTH_PROVIDER_ORIGINS[credential.provider]?.has(credential.origin) === true &&
|
|
419
|
+
credential.kind === "oauth" &&
|
|
420
|
+
credential.credential.type === "oauth"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function filterAutoImportOAuthCredentials(credentials: readonly ImportableCredential[]): ImportableCredential[] {
|
|
425
|
+
return credentials.filter(isAutoImportOAuthCredential);
|
|
426
|
+
}
|
|
427
|
+
|
|
411
428
|
/**
|
|
412
429
|
* Persist discovered credentials via `upsert`. Each credential is imported
|
|
413
430
|
* independently; a failure on one is recorded without aborting the rest.
|
|
@@ -15,6 +15,16 @@ These instructions teach a Hermes-style coordinator how to operate GJC through t
|
|
|
15
15
|
6. Use `{{TOOL_PREFIX}}_report_status` for coordinator-visible status and final reports.
|
|
16
16
|
7. Use `{{TOOL_PREFIX}}_read_tail` only as advisory debug output when structured turn state is insufficient.
|
|
17
17
|
|
|
18
|
+
## Prefer high-level delegation
|
|
19
|
+
|
|
20
|
+
When the goal is to hand GJC a whole workflow rather than micro-manage one prompt, prefer the first-class delegate tools over manual `{{TOOL_PREFIX}}_start_session` + `{{TOOL_PREFIX}}_send_prompt` sequencing:
|
|
21
|
+
|
|
22
|
+
- `gjc_delegate_plan` — run consensus planning (`/skill:ralplan`) to a pending-approval plan.
|
|
23
|
+
- `gjc_delegate_execute` — run execution (`/skill:ultragoal`) to completion with verification.
|
|
24
|
+
- `gjc_delegate_team` — run parallel team execution (`/skill:team`) with internal tmux workers.
|
|
25
|
+
|
|
26
|
+
Each delegate starts (or reuses) a session, sends one workflow-tagged turn, and returns a durable `turn_id`. Pass `cwd` and `task`; set `allow_mutation: true` only when the bridge startup mutation class is enabled and the user has approved changes. Poll the returned `turn_id` with `{{TOOL_PREFIX}}_await_turn` or watch for the `delegation.started` event, exactly as with `send_prompt`. Drop to the manual start/send tools only for fine-grained control the delegate tools do not cover.
|
|
27
|
+
|
|
18
28
|
## Event watch
|
|
19
29
|
|
|
20
30
|
`{{TOOL_PREFIX}}_watch_events` is a bounded long-poll read tool. Call it with `after_seq` set to the last stored sequence number, optional `session_id` or `event_types`, `timeout_ms` up to 30000, and `limit` up to 100. Store the returned `latest_seq` before the next wait. A timeout with no events is not failure; call again or use the turn/status read tools for a snapshot.
|
|
@@ -404,7 +404,7 @@ async function installConfig(spec: CoordinatorSetupSpec, force: boolean): Promis
|
|
|
404
404
|
|
|
405
405
|
async function runSmoke(spec: CoordinatorSetupSpec): Promise<HermesSetupResult["smoke"]> {
|
|
406
406
|
const requiredTools = [...COORDINATOR_MCP_TOOL_NAMES];
|
|
407
|
-
const server = createCoordinatorMcpServer({ env:
|
|
407
|
+
const server = createCoordinatorMcpServer({ env: renderHermesServerBlock(spec).env as NodeJS.ProcessEnv });
|
|
408
408
|
const listed = await server.handleJsonRpc({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} });
|
|
409
409
|
const listedResult = isRecord(listed.result) ? listed.result : {};
|
|
410
410
|
const tools = Array.isArray(listedResult.tools) ? listedResult.tools : [];
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host plugin setup for `gjc setup claude` and `gjc setup codex`.
|
|
3
|
+
*
|
|
4
|
+
* Renders install guidance and a fail-closed coordinator MCP config preview for
|
|
5
|
+
* the canonical generated plugin bundle under `plugins/`. This is intentionally
|
|
6
|
+
* render-only and fail-closed: the workdir allowlist is scoped to the project
|
|
7
|
+
* root and no mutation class is enabled until the user opts in.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
12
|
+
import { getProjectDir } from "@gajae-code/utils";
|
|
13
|
+
|
|
14
|
+
export type HostPluginKind = "claude" | "codex";
|
|
15
|
+
|
|
16
|
+
export interface HostPluginSetupFlags {
|
|
17
|
+
json?: boolean;
|
|
18
|
+
check?: boolean;
|
|
19
|
+
root?: string[];
|
|
20
|
+
repo?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface HostPluginSetupResult {
|
|
24
|
+
ok: true;
|
|
25
|
+
host: HostPluginKind;
|
|
26
|
+
mode: "render";
|
|
27
|
+
gated: boolean;
|
|
28
|
+
pluginPath: string;
|
|
29
|
+
manifestPath: string;
|
|
30
|
+
marketplacePath: string;
|
|
31
|
+
installGuidance: string[];
|
|
32
|
+
coordinatorConfigPreview: {
|
|
33
|
+
command: string;
|
|
34
|
+
args: string[];
|
|
35
|
+
env: Record<string, string>;
|
|
36
|
+
};
|
|
37
|
+
mutationPolicy: string;
|
|
38
|
+
notes: string[];
|
|
39
|
+
check?: { ok: boolean; checked: string[]; missing: string[] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const NAMESPACE_LABEL = "gajae-code-plugin";
|
|
43
|
+
|
|
44
|
+
function resolveProjectRoot(flags: HostPluginSetupFlags): string {
|
|
45
|
+
const explicit = flags.root?.find(root => root.trim().length > 0);
|
|
46
|
+
return explicit ? path.resolve(explicit) : getProjectDir();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function verifyBundleFiles(files: string[]): { ok: boolean; checked: string[]; missing: string[] } {
|
|
50
|
+
const missing = files.filter(file => !fs.existsSync(file));
|
|
51
|
+
return { ok: missing.length === 0, checked: files, missing };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildHostPluginSetup(host: HostPluginKind, flags: HostPluginSetupFlags = {}): HostPluginSetupResult {
|
|
55
|
+
const projectRoot = resolveProjectRoot(flags);
|
|
56
|
+
const marketplaceRoot = path.join(projectRoot, "plugins");
|
|
57
|
+
const pluginDir = path.join(marketplaceRoot, "gajae-code");
|
|
58
|
+
const repo = flags.repo && flags.repo.trim().length > 0 ? flags.repo.trim() : NAMESPACE_LABEL;
|
|
59
|
+
|
|
60
|
+
// Concrete, fail-closed env: workdir allowlist is the project root, no mutations.
|
|
61
|
+
const env: Record<string, string> = {
|
|
62
|
+
GJC_COORDINATOR_MCP_WORKDIR_ROOTS: projectRoot,
|
|
63
|
+
GJC_COORDINATOR_MCP_REPO: repo,
|
|
64
|
+
GJC_COORDINATOR_MCP_SESSION_COMMAND: "gjc --worktree",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
if (host === "claude") {
|
|
68
|
+
const manifestPath = path.join(pluginDir, ".claude-plugin", "plugin.json");
|
|
69
|
+
const marketplacePath = path.join(marketplaceRoot, ".claude-plugin", "marketplace.json");
|
|
70
|
+
return {
|
|
71
|
+
ok: true,
|
|
72
|
+
host,
|
|
73
|
+
mode: "render",
|
|
74
|
+
gated: false,
|
|
75
|
+
pluginPath: marketplaceRoot,
|
|
76
|
+
manifestPath,
|
|
77
|
+
marketplacePath,
|
|
78
|
+
installGuidance: [
|
|
79
|
+
`Add the local marketplace: /plugin marketplace add ${marketplaceRoot}`,
|
|
80
|
+
"Install the plugin: /plugin install gajae-code",
|
|
81
|
+
"Then call gjc_delegate_plan / gjc_delegate_execute / gjc_delegate_team from Claude Code.",
|
|
82
|
+
],
|
|
83
|
+
coordinatorConfigPreview: { command: "gjc", args: ["mcp-serve", "coordinator"], env },
|
|
84
|
+
mutationPolicy:
|
|
85
|
+
"Fail-closed: delegation is read-only until you set GJC_COORDINATOR_MCP_MUTATIONS=sessions and pass allow_mutation:true per call.",
|
|
86
|
+
notes: [],
|
|
87
|
+
...(flags.check
|
|
88
|
+
? { check: verifyBundleFiles([manifestPath, marketplacePath, path.join(pluginDir, ".mcp.json")]) }
|
|
89
|
+
: {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Codex: verified installable on Codex CLI 0.139.0 via the local marketplace smoke.
|
|
94
|
+
const manifestPath = path.join(pluginDir, ".codex-plugin", "plugin.json");
|
|
95
|
+
const marketplacePath = path.join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
host,
|
|
99
|
+
mode: "render",
|
|
100
|
+
gated: false,
|
|
101
|
+
pluginPath: marketplaceRoot,
|
|
102
|
+
manifestPath,
|
|
103
|
+
marketplacePath,
|
|
104
|
+
installGuidance: [
|
|
105
|
+
`Add the local marketplace: codex plugin marketplace add ${marketplaceRoot}`,
|
|
106
|
+
"Install the plugin: codex plugin add gajae-code@gajae-code-local",
|
|
107
|
+
"Then call gjc_delegate_plan / gjc_delegate_execute / gjc_delegate_team from Codex.",
|
|
108
|
+
],
|
|
109
|
+
coordinatorConfigPreview: { command: "gjc", args: ["mcp-serve", "coordinator"], env },
|
|
110
|
+
mutationPolicy:
|
|
111
|
+
"Fail-closed: delegation is read-only until you set GJC_COORDINATOR_MCP_MUTATIONS=sessions and pass allow_mutation:true per call.",
|
|
112
|
+
notes: [
|
|
113
|
+
"Verified on Codex CLI 0.139.0: marketplace add + plugin add install the plugin (enabled) and `codex mcp list` registers gjc-coordinator with the fail-closed env.",
|
|
114
|
+
"The bundled .codex.mcp.json workdir root is host-neutral; `gjc setup codex` renders a concrete root, and operators should re-run the local marketplace smoke on their target Codex version.",
|
|
115
|
+
],
|
|
116
|
+
...(flags.check
|
|
117
|
+
? {
|
|
118
|
+
check: verifyBundleFiles([
|
|
119
|
+
manifestPath,
|
|
120
|
+
marketplacePath,
|
|
121
|
+
path.join(pluginDir, ".codex.mcp.json"),
|
|
122
|
+
path.join(pluginDir, "skills", "gjc-delegation", "SKILL.md"),
|
|
123
|
+
]),
|
|
124
|
+
}
|
|
125
|
+
: {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function formatHostPluginSetup(result: HostPluginSetupResult): string {
|
|
130
|
+
const lines: string[] = [];
|
|
131
|
+
lines.push(`host: ${result.host}${result.gated ? " (gated on versioned smoke)" : ""}`);
|
|
132
|
+
lines.push(`plugin: ${result.pluginPath}`);
|
|
133
|
+
lines.push("install:");
|
|
134
|
+
for (const step of result.installGuidance) lines.push(` - ${step}`);
|
|
135
|
+
lines.push(`mcp: ${result.coordinatorConfigPreview.command} ${result.coordinatorConfigPreview.args.join(" ")}`);
|
|
136
|
+
lines.push(
|
|
137
|
+
` GJC_COORDINATOR_MCP_WORKDIR_ROOTS=${result.coordinatorConfigPreview.env.GJC_COORDINATOR_MCP_WORKDIR_ROOTS}`,
|
|
138
|
+
);
|
|
139
|
+
lines.push(result.mutationPolicy);
|
|
140
|
+
for (const note of result.notes) lines.push(`note: ${note}`);
|
|
141
|
+
return lines.join("\n");
|
|
142
|
+
}
|