@gajae-code/coding-agent 0.4.3 → 0.4.5
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 +42 -0
- package/dist/types/async/job-manager.d.ts +19 -1
- package/dist/types/cli/fast-help.d.ts +1 -0
- package/dist/types/cli/setup-cli.d.ts +16 -1
- package/dist/types/commands/coordinator.d.ts +19 -0
- package/dist/types/commands/harness.d.ts +3 -0
- package/dist/types/commands/mcp-serve.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +47 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/coordinator/contract.d.ts +4 -0
- package/dist/types/coordinator-mcp/policy.d.ts +24 -0
- package/dist/types/coordinator-mcp/safety.d.ts +26 -0
- package/dist/types/coordinator-mcp/server.d.ts +58 -0
- package/dist/types/extensibility/extensions/types.d.ts +13 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/harness-control-plane/finalize.d.ts +5 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +46 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/types.d.ts +9 -1
- package/dist/types/main.d.ts +2 -2
- package/dist/types/modes/components/hook-selector.d.ts +11 -0
- package/dist/types/modes/utils/abort-message.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +8 -0
- package/dist/types/setup/hermes-setup.d.ts +78 -0
- package/dist/types/task/fork-context-advisory.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +1 -0
- package/dist/types/task/render.d.ts +7 -1
- package/dist/types/task/roi-reconciliation.d.ts +27 -0
- package/dist/types/task/types.d.ts +10 -0
- package/dist/types/tools/subagent-render.d.ts +25 -0
- package/dist/types/tools/subagent.d.ts +5 -1
- package/package.json +8 -7
- package/scripts/build-binary.ts +4 -0
- package/src/async/job-manager.ts +43 -1
- package/src/cli/fast-help.ts +80 -0
- package/src/cli/setup-cli.ts +95 -2
- package/src/cli.ts +109 -16
- package/src/commands/coordinator.ts +113 -0
- package/src/commands/harness.ts +92 -9
- package/src/commands/mcp-serve.ts +63 -0
- package/src/commands/setup.ts +34 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/coordinator/contract.ts +21 -0
- package/src/coordinator-mcp/policy.ts +160 -0
- package/src/coordinator-mcp/safety.ts +80 -0
- package/src/coordinator-mcp/server.ts +1519 -0
- package/src/cursor.ts +30 -2
- package/src/extensibility/extensions/types.ts +13 -0
- package/src/gjc-runtime/launch-worktree.ts +12 -1
- package/src/gjc-runtime/session-state-sidecar.ts +117 -0
- package/src/harness-control-plane/finalize.ts +39 -5
- package/src/harness-control-plane/owner.ts +9 -1
- package/src/harness-control-plane/phase-rollup.ts +96 -0
- package/src/harness-control-plane/receipt-ingest.ts +127 -0
- package/src/harness-control-plane/receipts.ts +229 -1
- package/src/harness-control-plane/rpc-adapter.ts +8 -0
- package/src/harness-control-plane/types.ts +29 -1
- package/src/internal-urls/docs-index.generated.ts +6 -4
- package/src/main.ts +7 -3
- package/src/modes/components/hook-selector.ts +109 -5
- package/src/modes/components/status-line.ts +6 -6
- package/src/modes/controllers/event-controller.ts +5 -4
- package/src/modes/controllers/extension-ui-controller.ts +16 -1
- package/src/modes/interactive-mode.ts +4 -5
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/utils/abort-message.ts +41 -0
- package/src/modes/utils/context-usage.ts +15 -8
- package/src/modes/utils/ui-helpers.ts +5 -6
- package/src/prompts/agents/architect.md +6 -0
- package/src/prompts/agents/critic.md +6 -0
- package/src/prompts/agents/planner.md +8 -1
- package/src/sdk.ts +9 -4
- package/src/session/agent-session.ts +22 -5
- package/src/session/session-manager.ts +20 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +30 -0
- package/src/setup/hermes-setup.ts +484 -0
- package/src/task/fork-context-advisory.ts +99 -0
- package/src/task/index.ts +33 -2
- package/src/task/receipt.ts +2 -0
- package/src/task/render.ts +14 -0
- package/src/task/roi-reconciliation.ts +90 -0
- package/src/task/types.ts +7 -0
- package/src/tools/ask.ts +30 -10
- package/src/tools/index.ts +2 -2
- package/src/tools/renderers.ts +2 -0
- package/src/tools/subagent-render.ts +169 -0
- package/src/tools/subagent.ts +49 -7
- package/src/utils/title-generator.ts +16 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export type CoordinatorMutationClass = "sessions" | "questions" | "reports";
|
|
5
|
+
|
|
6
|
+
export interface CoordinatorNamespace {
|
|
7
|
+
profile: string | null;
|
|
8
|
+
repo: string | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CoordinatorMcpConfig {
|
|
12
|
+
allowedRoots: string[];
|
|
13
|
+
mutationClasses: Set<CoordinatorMutationClass>;
|
|
14
|
+
artifactByteCap: number;
|
|
15
|
+
namespace: CoordinatorNamespace;
|
|
16
|
+
stateRoot: string;
|
|
17
|
+
sessionCommand: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CoordinatorMutationRequest {
|
|
21
|
+
allow_mutation?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DEFAULT_ARTIFACT_BYTE_CAP = 64 * 1024;
|
|
25
|
+
const MAX_ARTIFACT_BYTE_CAP = 1024 * 1024;
|
|
26
|
+
const MUTATION_CLASSES = new Set<CoordinatorMutationClass>(["sessions", "questions", "reports"]);
|
|
27
|
+
const LEGACY_MUTATION_CLASS_ALIASES = new Map<string, CoordinatorMutationClass>([
|
|
28
|
+
["session", "sessions"],
|
|
29
|
+
["prompt", "sessions"],
|
|
30
|
+
["question", "questions"],
|
|
31
|
+
["report", "reports"],
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
function parseList(value: string | undefined): string[] {
|
|
35
|
+
return (value ?? "")
|
|
36
|
+
.split(/[\n,;:]+/)
|
|
37
|
+
.map(part => part.trim())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseRootList(value: string | undefined): string[] {
|
|
42
|
+
const normalized = (value ?? "").replace(/[\n,;]+/g, path.delimiter);
|
|
43
|
+
return normalized
|
|
44
|
+
.split(path.delimiter)
|
|
45
|
+
.map(part => part.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseMutationClasses(value: string | undefined): Set<CoordinatorMutationClass> {
|
|
50
|
+
const classes = new Set<CoordinatorMutationClass>();
|
|
51
|
+
for (const raw of parseList(value)) {
|
|
52
|
+
const normalized = raw.toLowerCase();
|
|
53
|
+
if (normalized === "all") {
|
|
54
|
+
for (const mutationClass of MUTATION_CLASSES) classes.add(mutationClass);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const mutationClass = LEGACY_MUTATION_CLASS_ALIASES.get(normalized) ?? normalized;
|
|
58
|
+
if (MUTATION_CLASSES.has(mutationClass as CoordinatorMutationClass))
|
|
59
|
+
classes.add(mutationClass as CoordinatorMutationClass);
|
|
60
|
+
}
|
|
61
|
+
return classes;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseByteCap(value: string | undefined): number {
|
|
65
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
66
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_ARTIFACT_BYTE_CAP;
|
|
67
|
+
return Math.min(parsed, MAX_ARTIFACT_BYTE_CAP);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function cleanScope(value: string | undefined): string | null {
|
|
71
|
+
const trimmed = value?.trim();
|
|
72
|
+
if (!trimmed) return null;
|
|
73
|
+
return trimmed.replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 100) || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function buildCoordinatorMcpConfig(env: NodeJS.ProcessEnv = process.env): CoordinatorMcpConfig {
|
|
77
|
+
const stateRoot =
|
|
78
|
+
env.GJC_COORDINATOR_MCP_STATE_ROOT?.trim() || path.join(process.cwd(), ".gjc", "state", "coordinator-mcp");
|
|
79
|
+
return {
|
|
80
|
+
allowedRoots: parseRootList(env.GJC_COORDINATOR_MCP_WORKDIR_ROOTS).map(root => path.resolve(root)),
|
|
81
|
+
mutationClasses: parseMutationClasses(
|
|
82
|
+
env.GJC_COORDINATOR_MCP_MUTATIONS ?? env.GJC_COORDINATOR_MCP_ENABLE_MUTATION_CLASSES,
|
|
83
|
+
),
|
|
84
|
+
artifactByteCap: parseByteCap(
|
|
85
|
+
env.GJC_COORDINATOR_MCP_ARTIFACT_BYTE_CAP ?? env.GJC_COORDINATOR_MCP_ARTIFACT_MAX_BYTES,
|
|
86
|
+
),
|
|
87
|
+
namespace: {
|
|
88
|
+
profile: cleanScope(env.GJC_COORDINATOR_MCP_PROFILE),
|
|
89
|
+
repo: cleanScope(env.GJC_COORDINATOR_MCP_REPO),
|
|
90
|
+
},
|
|
91
|
+
stateRoot: path.resolve(stateRoot),
|
|
92
|
+
sessionCommand: env.GJC_COORDINATOR_MCP_SESSION_COMMAND?.trim() || null,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function realpathIfExists(value: string): Promise<string> {
|
|
97
|
+
try {
|
|
98
|
+
return await fs.realpath(value);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
101
|
+
const parent = await fs.realpath(path.dirname(value));
|
|
102
|
+
return path.join(parent, path.basename(value));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isInside(candidate: string, root: string): boolean {
|
|
107
|
+
const relative = path.relative(root, candidate);
|
|
108
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function canonicalAllowedRoots(config: CoordinatorMcpConfig): Promise<string[]> {
|
|
112
|
+
const roots = await Promise.all(config.allowedRoots.map(root => realpathIfExists(root)));
|
|
113
|
+
return roots.map(root => path.resolve(root));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function assertCoordinatorWorkdir(config: CoordinatorMcpConfig, cwd: unknown): Promise<string> {
|
|
117
|
+
if (typeof cwd !== "string" || cwd.trim().length === 0) throw new Error("coordinator_workdir_required");
|
|
118
|
+
if (config.allowedRoots.length === 0) throw new Error("coordinator_workdir_roots_required");
|
|
119
|
+
const requested = path.resolve(cwd);
|
|
120
|
+
const canonicalRequested = await realpathIfExists(requested);
|
|
121
|
+
const roots = await canonicalAllowedRoots(config);
|
|
122
|
+
if (!roots.some(root => isInside(canonicalRequested, root))) {
|
|
123
|
+
throw new Error(`coordinator_workdir_outside_allowed_roots:${requested}`);
|
|
124
|
+
}
|
|
125
|
+
return requested;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function assertCoordinatorArtifactPath(
|
|
129
|
+
config: CoordinatorMcpConfig,
|
|
130
|
+
artifactPath: unknown,
|
|
131
|
+
): Promise<{ path: string; byteCap: number }> {
|
|
132
|
+
if (typeof artifactPath !== "string" || artifactPath.trim().length === 0)
|
|
133
|
+
throw new Error("coordinator_artifact_path_required");
|
|
134
|
+
if (config.allowedRoots.length === 0) throw new Error("coordinator_artifact_roots_required");
|
|
135
|
+
const requested = path.resolve(artifactPath);
|
|
136
|
+
const canonicalRequested = await realpathIfExists(requested);
|
|
137
|
+
const roots = await canonicalAllowedRoots(config);
|
|
138
|
+
if (!roots.some(root => isInside(canonicalRequested, root))) {
|
|
139
|
+
throw new Error(`coordinator_artifact_outside_allowed_roots:${requested}`);
|
|
140
|
+
}
|
|
141
|
+
return { path: requested, byteCap: config.artifactByteCap };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function requireCoordinatorMutation(
|
|
145
|
+
config: CoordinatorMcpConfig,
|
|
146
|
+
mutationClass: CoordinatorMutationClass,
|
|
147
|
+
request: CoordinatorMutationRequest,
|
|
148
|
+
): void {
|
|
149
|
+
if (!config.mutationClasses.has(mutationClass))
|
|
150
|
+
throw new Error(`coordinator_mutation_class_disabled:${mutationClass}`);
|
|
151
|
+
if (request.allow_mutation !== true) throw new Error(`coordinator_mutation_call_not_allowed:${mutationClass}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function coordinatorNamespacePath(config: CoordinatorMcpConfig): string {
|
|
155
|
+
return path.join(
|
|
156
|
+
config.stateRoot,
|
|
157
|
+
config.namespace.profile ?? "unscoped-profile",
|
|
158
|
+
config.namespace.repo ?? "unscoped-repo",
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertCoordinatorArtifactPath,
|
|
3
|
+
assertCoordinatorWorkdir,
|
|
4
|
+
buildCoordinatorMcpConfig,
|
|
5
|
+
type CoordinatorMcpConfig,
|
|
6
|
+
type CoordinatorMutationClass,
|
|
7
|
+
requireCoordinatorMutation,
|
|
8
|
+
} from "./policy";
|
|
9
|
+
|
|
10
|
+
export const COORDINATOR_MUTATION_CLASSES = ["sessions", "questions", "reports"] as const;
|
|
11
|
+
|
|
12
|
+
export type { CoordinatorMutationClass };
|
|
13
|
+
|
|
14
|
+
export interface CoordinatorSafetyConfig {
|
|
15
|
+
allowedRoots: string[];
|
|
16
|
+
artifactMaxBytes: number;
|
|
17
|
+
enabledMutationClasses: Set<CoordinatorMutationClass>;
|
|
18
|
+
repo?: string;
|
|
19
|
+
profile?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CoordinatorSafetyPolicy {
|
|
23
|
+
config: CoordinatorSafetyConfig;
|
|
24
|
+
resolveWorkdir(input: unknown): Promise<string>;
|
|
25
|
+
resolveArtifactPath(input: unknown): Promise<string>;
|
|
26
|
+
assertMutationAllowed(
|
|
27
|
+
mutationClass: CoordinatorMutationClass,
|
|
28
|
+
args: Record<string, unknown>,
|
|
29
|
+
): { ok: true } | CoordinatorFailure;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface CoordinatorFailure {
|
|
33
|
+
ok: false;
|
|
34
|
+
reason: string;
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toSafetyConfig(config: CoordinatorMcpConfig): CoordinatorSafetyConfig {
|
|
39
|
+
return {
|
|
40
|
+
allowedRoots: config.allowedRoots,
|
|
41
|
+
artifactMaxBytes: config.artifactByteCap,
|
|
42
|
+
enabledMutationClasses: config.mutationClasses,
|
|
43
|
+
repo: config.namespace.repo ?? undefined,
|
|
44
|
+
profile: config.namespace.profile ?? undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toFailure(error: unknown): CoordinatorFailure {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
const [rawReason, detail] = message.split(":", 2);
|
|
51
|
+
const reason = rawReason.replace(/^coordinator_/, "");
|
|
52
|
+
return detail === undefined ? { ok: false, reason } : { ok: false, reason, detail };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function createCoordinatorSafetyPolicy(
|
|
56
|
+
options: { env?: NodeJS.ProcessEnv } = {},
|
|
57
|
+
): Promise<CoordinatorSafetyPolicy> {
|
|
58
|
+
const canonicalConfig = buildCoordinatorMcpConfig(options.env ?? process.env);
|
|
59
|
+
const config = toSafetyConfig(canonicalConfig);
|
|
60
|
+
return {
|
|
61
|
+
config,
|
|
62
|
+
resolveWorkdir(input: unknown): Promise<string> {
|
|
63
|
+
return assertCoordinatorWorkdir(canonicalConfig, input);
|
|
64
|
+
},
|
|
65
|
+
async resolveArtifactPath(input: unknown): Promise<string> {
|
|
66
|
+
return (await assertCoordinatorArtifactPath(canonicalConfig, input)).path;
|
|
67
|
+
},
|
|
68
|
+
assertMutationAllowed(
|
|
69
|
+
mutationClass: CoordinatorMutationClass,
|
|
70
|
+
args: Record<string, unknown>,
|
|
71
|
+
): { ok: true } | CoordinatorFailure {
|
|
72
|
+
try {
|
|
73
|
+
requireCoordinatorMutation(canonicalConfig, mutationClass, args);
|
|
74
|
+
return { ok: true };
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return toFailure(error);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|