@kitman-sh/cli 0.7.3 → 0.7.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/dist/{chunk-80g9af03.js → chunk-0pre510a.js} +2 -2
- package/dist/{chunk-c5nwtn11.js → chunk-1f7qe00x.js} +4 -4
- package/dist/{chunk-1n61xbaq.js → chunk-4gc7b9qt.js} +2 -2
- package/dist/{chunk-nvf0begz.js → chunk-64cz9grx.js} +36 -42
- package/dist/{chunk-0296rete.js → chunk-a44szh8v.js} +12 -12
- package/dist/{chunk-yysejjz0.js → chunk-hpfcwnd9.js} +9 -9
- package/dist/{chunk-8g2twwm8.js → chunk-p2wk2v39.js} +512 -262
- package/dist/{chunk-x6j2j12j.js → chunk-sq2brwmh.js} +1 -1
- package/dist/{chunk-84t0s0s2.js → chunk-tk0gy9wq.js} +5 -5
- package/dist/{chunk-kt08et8q.js → chunk-vz3v0f4j.js} +718 -263
- package/dist/{chunk-zc0ebz8f.js → chunk-xnzxpert.js} +3 -3
- package/dist/index.js +133 -247
- package/package.json +1 -1
|
@@ -13,10 +13,13 @@ import {
|
|
|
13
13
|
none,
|
|
14
14
|
isNone,
|
|
15
15
|
isSome,
|
|
16
|
+
match,
|
|
16
17
|
getOrUndefined,
|
|
18
|
+
flatMap,
|
|
17
19
|
Service,
|
|
18
20
|
get,
|
|
19
21
|
merge,
|
|
22
|
+
Reference,
|
|
20
23
|
succeed,
|
|
21
24
|
fail,
|
|
22
25
|
isResult,
|
|
@@ -36,11 +39,14 @@ import {
|
|
|
36
39
|
isSuccess2,
|
|
37
40
|
isFailure2,
|
|
38
41
|
makeUnsafe,
|
|
42
|
+
make6,
|
|
39
43
|
_await,
|
|
44
|
+
fail3,
|
|
40
45
|
isDoneUnsafe,
|
|
46
|
+
succeed3,
|
|
41
47
|
doneUnsafe,
|
|
42
48
|
Scope,
|
|
43
|
-
|
|
49
|
+
make7,
|
|
44
50
|
provide,
|
|
45
51
|
addFinalizer,
|
|
46
52
|
close,
|
|
@@ -61,7 +67,7 @@ import {
|
|
|
61
67
|
fail4,
|
|
62
68
|
die3,
|
|
63
69
|
withFiber,
|
|
64
|
-
|
|
70
|
+
flatMap2,
|
|
65
71
|
andThen,
|
|
66
72
|
tap,
|
|
67
73
|
result,
|
|
@@ -88,6 +94,7 @@ import {
|
|
|
88
94
|
matchEffect,
|
|
89
95
|
context,
|
|
90
96
|
contextWith,
|
|
97
|
+
provide3,
|
|
91
98
|
provideContext,
|
|
92
99
|
updateContext,
|
|
93
100
|
provideService,
|
|
@@ -106,28 +113,38 @@ import {
|
|
|
106
113
|
logError,
|
|
107
114
|
interrupt3,
|
|
108
115
|
makeUnsafe3,
|
|
116
|
+
unbounded,
|
|
117
|
+
offer,
|
|
109
118
|
makeUnsafe4,
|
|
110
|
-
|
|
119
|
+
make11,
|
|
120
|
+
fromQueue,
|
|
111
121
|
map4,
|
|
112
122
|
filter,
|
|
113
123
|
decodeText,
|
|
114
124
|
splitLines,
|
|
115
125
|
runHead,
|
|
126
|
+
runForEach,
|
|
116
127
|
FileSystem,
|
|
117
128
|
Path,
|
|
118
129
|
ChildProcessSpawner,
|
|
119
|
-
|
|
130
|
+
make13,
|
|
120
131
|
Crypto,
|
|
121
132
|
value,
|
|
122
133
|
is,
|
|
134
|
+
decodeUnknownEffect,
|
|
135
|
+
decodeUnknownOption,
|
|
123
136
|
optionalKey,
|
|
137
|
+
optional,
|
|
124
138
|
Literal,
|
|
139
|
+
Unknown,
|
|
125
140
|
String2,
|
|
126
141
|
Boolean2,
|
|
127
142
|
Struct,
|
|
128
143
|
ArraySchema,
|
|
129
144
|
Union,
|
|
130
145
|
Literals,
|
|
146
|
+
NullOr,
|
|
147
|
+
Finite,
|
|
131
148
|
Int,
|
|
132
149
|
Date2,
|
|
133
150
|
TaggedError2,
|
|
@@ -136,6 +153,7 @@ import {
|
|
|
136
153
|
findForbiddenRepoBindingSecretPath,
|
|
137
154
|
runnerAgentSandboxPolicy,
|
|
138
155
|
agentNetworkAllowlist,
|
|
156
|
+
AgentHarnessIds,
|
|
139
157
|
findForbiddenAgentEnvironmentSecretPath,
|
|
140
158
|
redactedOutputTail,
|
|
141
159
|
canonicalSha256,
|
|
@@ -147,12 +165,17 @@ import {
|
|
|
147
165
|
encodePrettyJsonString,
|
|
148
166
|
decodeJsonStringSync,
|
|
149
167
|
encodeUnknownJsonStringSync,
|
|
168
|
+
schemaBodyJson,
|
|
169
|
+
filterStatusOk,
|
|
170
|
+
get4,
|
|
171
|
+
layer4,
|
|
150
172
|
digestSha256,
|
|
151
173
|
RunSettled,
|
|
152
174
|
CommandFailed2,
|
|
153
175
|
runCommand2,
|
|
154
176
|
shellOutputTailChars2,
|
|
155
177
|
runProcess2,
|
|
178
|
+
CommandRunner2,
|
|
156
179
|
runShell2,
|
|
157
180
|
optionalEnv,
|
|
158
181
|
detectHarnesses2,
|
|
@@ -161,7 +184,7 @@ import {
|
|
|
161
184
|
herdrRunnerCapabilitiesForRepoBinding2,
|
|
162
185
|
herdrRunnerCapabilities2,
|
|
163
186
|
assertHerdrRunnerCapability2
|
|
164
|
-
} from "./chunk-
|
|
187
|
+
} from "./chunk-p2wk2v39.js";
|
|
165
188
|
import {
|
|
166
189
|
make4,
|
|
167
190
|
readerString,
|
|
@@ -172,17 +195,17 @@ import {
|
|
|
172
195
|
SocketUpgradeError,
|
|
173
196
|
SocketCloseError,
|
|
174
197
|
SocketError
|
|
175
|
-
} from "./chunk-
|
|
176
|
-
import"./chunk-
|
|
198
|
+
} from "./chunk-tk0gy9wq.js";
|
|
199
|
+
import"./chunk-a44szh8v.js";
|
|
177
200
|
import {
|
|
178
201
|
programLease,
|
|
179
202
|
programHostsEnv,
|
|
180
203
|
undeclaredHostsEnv
|
|
181
|
-
} from "./chunk-
|
|
182
|
-
import"./chunk-
|
|
204
|
+
} from "./chunk-4gc7b9qt.js";
|
|
205
|
+
import"./chunk-1f7qe00x.js";
|
|
183
206
|
import {
|
|
184
207
|
nextInt
|
|
185
|
-
} from "./chunk-
|
|
208
|
+
} from "./chunk-sq2brwmh.js";
|
|
186
209
|
|
|
187
210
|
// packages/core/src/runner-trust.ts
|
|
188
211
|
import { createHash } from "crypto";
|
|
@@ -195,129 +218,6 @@ function sha256(value) {
|
|
|
195
218
|
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
196
219
|
}
|
|
197
220
|
|
|
198
|
-
// packages/core/src/engineering-resources.ts
|
|
199
|
-
var marketplace = {
|
|
200
|
-
source: "kitman-engineering-loop",
|
|
201
|
-
publisher: "Kitman Platform",
|
|
202
|
-
visibility: "public",
|
|
203
|
-
trusted: true,
|
|
204
|
-
reviewStatus: "approved",
|
|
205
|
-
installable: true,
|
|
206
|
-
rollout: { channel: "default", installDefault: true, preservesExistingInstallations: true }
|
|
207
|
-
};
|
|
208
|
-
var kit = (input) => ({
|
|
209
|
-
...input,
|
|
210
|
-
metadata: { marketplace }
|
|
211
|
-
});
|
|
212
|
-
var MattPocockProcessKit = kit({
|
|
213
|
-
name: "Matt Pocock Process",
|
|
214
|
-
slug: "matt-pocock-process",
|
|
215
|
-
kind: "process",
|
|
216
|
-
description: "Adversarial, type-safe implementation and review practice for Kitman Engineering.",
|
|
217
|
-
contentMarkdown: [
|
|
218
|
-
"# Matt Pocock Process",
|
|
219
|
-
"",
|
|
220
|
-
"Use fresh-context review, TDD, Effect-native boundaries, and explicit type contracts.",
|
|
221
|
-
"",
|
|
222
|
-
"## Complete skill collection",
|
|
223
|
-
"",
|
|
224
|
-
"diagnosing-bugs, tdd, effect-ts, codebase-design, api-and-interface-design, improve-codebase-architecture, doubt-driven-development, documentation-and-adrs, postgres-drizzle, security-and-hardening, source-driven-development, test-driven-development, research."
|
|
225
|
-
].join(`
|
|
226
|
-
`),
|
|
227
|
-
agentEnvironment: {
|
|
228
|
-
skills: [
|
|
229
|
-
{
|
|
230
|
-
name: "research",
|
|
231
|
-
policy: "required",
|
|
232
|
-
source: {
|
|
233
|
-
kind: "git",
|
|
234
|
-
url: "https://github.com/mattpocock/skills.git",
|
|
235
|
-
ref: "49dd158d1076134a641b33efb035946536778336",
|
|
236
|
-
path: "skills/engineering/research"
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
]
|
|
240
|
-
},
|
|
241
|
-
agentInstructions: "Prefer the smallest correct change, preserve evidence, and stop on ambiguous public-interface decisions.",
|
|
242
|
-
doNotDo: "Do not execute user-controlled code in the API or Workflow worker."
|
|
243
|
-
});
|
|
244
|
-
var AddyOsmaniProcessKit = kit({
|
|
245
|
-
name: "Addy Osmani Process",
|
|
246
|
-
slug: "addy-osmani-process",
|
|
247
|
-
kind: "process",
|
|
248
|
-
description: "Incremental, accessible, observable, and production-focused delivery practice for Kitman Engineering.",
|
|
249
|
-
contentMarkdown: [
|
|
250
|
-
"# Addy Osmani Process",
|
|
251
|
-
"",
|
|
252
|
-
"Use incremental implementation, browser evidence, accessibility checks, CI discipline, and simplification.",
|
|
253
|
-
"",
|
|
254
|
-
"## Complete skill collection",
|
|
255
|
-
"",
|
|
256
|
-
"incremental-implementation, frontend-ui-engineering, browser-testing-with-devtools, web-design-guidelines, ci-cd-and-automation, code-review-and-quality, code-simplification, observability-and-instrumentation, performance-optimization, shipping-and-launch, git-workflow-and-versioning."
|
|
257
|
-
].join(`
|
|
258
|
-
`),
|
|
259
|
-
agentInstructions: "Prefer verifiable slices, accessible interfaces, and evidence that survives handoff.",
|
|
260
|
-
doNotDo: "Do not hide failures behind broad fallbacks or claim production evidence from local fixtures."
|
|
261
|
-
});
|
|
262
|
-
var PonytailProcessKit = kit({
|
|
263
|
-
name: "Ponytail",
|
|
264
|
-
slug: "ponytail",
|
|
265
|
-
kind: "process",
|
|
266
|
-
description: "Removes unnecessary code and complexity before implementation and review complete.",
|
|
267
|
-
contentMarkdown: `# Ponytail
|
|
268
|
-
|
|
269
|
-
Prefer deletion, existing code, and the smallest correct change. Avoid speculative abstractions.`,
|
|
270
|
-
agentEnvironment: {
|
|
271
|
-
plugins: [
|
|
272
|
-
{
|
|
273
|
-
name: "ponytail",
|
|
274
|
-
policy: "required",
|
|
275
|
-
source: {
|
|
276
|
-
kind: "package",
|
|
277
|
-
packageName: "@dietrichgebert/ponytail",
|
|
278
|
-
version: "4.9.0"
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
]
|
|
282
|
-
},
|
|
283
|
-
agentInstructions: "Use the required Ponytail skill before completing implementation or review.",
|
|
284
|
-
doNotDo: "Do not add an abstraction, dependency, or configuration without a present requirement."
|
|
285
|
-
});
|
|
286
|
-
var EngineeringTechnologyKitCatalog = [
|
|
287
|
-
kit({
|
|
288
|
-
name: "Backend",
|
|
289
|
-
slug: "backend",
|
|
290
|
-
kind: "technology",
|
|
291
|
-
description: "Effect, Elysia, PostgreSQL, Drizzle, Better Auth, and backend practices.",
|
|
292
|
-
contentMarkdown: `# Backend
|
|
293
|
-
|
|
294
|
-
Effect, Elysia, PostgreSQL, Drizzle, Better Auth, API contracts, security, and observability.`
|
|
295
|
-
}),
|
|
296
|
-
kit({
|
|
297
|
-
name: "Frontend",
|
|
298
|
-
slug: "frontend",
|
|
299
|
-
kind: "technology",
|
|
300
|
-
description: "Nuxt, Vue, Nuxt UI, TanStack Query, VueUse, accessibility, and browser testing.",
|
|
301
|
-
contentMarkdown: `# Frontend
|
|
302
|
-
|
|
303
|
-
Nuxt, Vue, Nuxt UI, TanStack Query, VueUse, accessibility, responsive UI, and browser testing.`
|
|
304
|
-
}),
|
|
305
|
-
kit({
|
|
306
|
-
name: "DevOps",
|
|
307
|
-
slug: "devops",
|
|
308
|
-
kind: "technology",
|
|
309
|
-
description: "Kubernetes, Kustomize, GitLab CI, and multi-stage container builds.",
|
|
310
|
-
contentMarkdown: `# DevOps
|
|
311
|
-
|
|
312
|
-
Kubernetes, Kustomize, GitLab CI, multi-stage container builds, and operational verification.`
|
|
313
|
-
})
|
|
314
|
-
];
|
|
315
|
-
var EngineeringProcessKitCatalog = [
|
|
316
|
-
MattPocockProcessKit,
|
|
317
|
-
AddyOsmaniProcessKit,
|
|
318
|
-
PonytailProcessKit
|
|
319
|
-
];
|
|
320
|
-
|
|
321
221
|
// packages/agent-runtime/src/agent-environment.ts
|
|
322
222
|
class AgentEnvironmentResolutionError extends TaggedError("AgentEnvironmentResolutionError") {
|
|
323
223
|
get message() {
|
|
@@ -647,7 +547,7 @@ var optionalString = (value, label) => gen(function* () {
|
|
|
647
547
|
return yield* invalid(`${label} must be a non-empty string when provided`);
|
|
648
548
|
});
|
|
649
549
|
// packages/agent-runtime/src/config-convergence.ts
|
|
650
|
-
var writeAgentEnvironmentConfigFile = fnUntraced(function* (file, content) {
|
|
550
|
+
var writeAgentEnvironmentConfigFile = fnUntraced(function* (file, content, keep = {}) {
|
|
651
551
|
const fs = yield* FileSystem;
|
|
652
552
|
const paths = {
|
|
653
553
|
path: yield* Path,
|
|
@@ -660,14 +560,16 @@ var writeAgentEnvironmentConfigFile = fnUntraced(function* (file, content) {
|
|
|
660
560
|
return mergeCodexAgents(existing, content);
|
|
661
561
|
if (file.endsWith("opencode.json")) {
|
|
662
562
|
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
663
|
-
return mergeOpenCodeConfig(existing, content, managed.mcpNames, managed.
|
|
563
|
+
return mergeOpenCodeConfig(existing, content, managed.mcpNames, without(managed.packageNames, keep.packages));
|
|
664
564
|
}
|
|
665
565
|
if (file.endsWith(".codex/config.toml")) {
|
|
666
566
|
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
667
|
-
return mergeCodexConfig(existing, content, managed.mcpNames, managed.skillPaths);
|
|
567
|
+
return mergeCodexConfig(existing, content, managed.mcpNames, managed.skillPaths, keep.kitmanPlugins === true);
|
|
568
|
+
}
|
|
569
|
+
if (isPiConfigPath(paths.path, file, "settings.json")) {
|
|
570
|
+
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
571
|
+
return mergePiSettingsConfig(existing, content, without(managed.packageNames, keep.packages));
|
|
668
572
|
}
|
|
669
|
-
if (isPiConfigPath(paths.path, file, "settings.json"))
|
|
670
|
-
return mergePiSettingsConfig(existing, content);
|
|
671
573
|
if (isPiConfigPath(paths.path, file, "mcp.json")) {
|
|
672
574
|
const managed = yield* readManagedNamesForConfig(paths, file);
|
|
673
575
|
return mergePiMcpConfig(existing, content, managed.mcpNames);
|
|
@@ -676,6 +578,7 @@ var writeAgentEnvironmentConfigFile = fnUntraced(function* (file, content) {
|
|
|
676
578
|
});
|
|
677
579
|
yield* fs.writeFileString(file, merged);
|
|
678
580
|
});
|
|
581
|
+
var without = (names, kept = []) => new Set([...names].filter((name) => !kept.includes(name)));
|
|
679
582
|
var KitmanAgentsStartMarker = "<!-- KITMAN_AGENT_ENVIRONMENT_START -->";
|
|
680
583
|
var KitmanAgentsEndMarker = "<!-- KITMAN_AGENT_ENVIRONMENT_END -->";
|
|
681
584
|
var KitmanGeneratedAgentsPrefix = "# Kitman AgentEnvironment\n\nThis file is generated by `kitman sync`";
|
|
@@ -698,27 +601,34 @@ ${KitmanAgentsEndMarker}`;
|
|
|
698
601
|
${block}
|
|
699
602
|
`;
|
|
700
603
|
}
|
|
701
|
-
var readExistingText = (file) =>
|
|
604
|
+
var readExistingText = (file) => flatMap2(FileSystem, (fs) => fs.readFileString(file).pipe(catchReason("PlatformError", "NotFound", () => succeed5(""))));
|
|
702
605
|
var readManagedNamesForConfig = (paths, file) => gen(function* () {
|
|
703
606
|
const manifest = parseJsonObject(yield* readExistingText(yield* managedManifestPathForConfig(paths, file)));
|
|
704
607
|
const servers = Array.isArray(manifest.mcpServers) ? manifest.mcpServers : [];
|
|
705
608
|
const plugins = Array.isArray(manifest.plugins) ? manifest.plugins : [];
|
|
706
|
-
const materializedSkills = Array.isArray(manifest.materializedSkills) ? manifest.materializedSkills : [];
|
|
707
609
|
return {
|
|
708
610
|
mcpNames: new Set(servers.flatMap((entry) => {
|
|
709
611
|
const item = objectValue(objectValue(entry).item);
|
|
710
612
|
return isString2(item.name) ? [item.name] : [];
|
|
711
613
|
})),
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
return
|
|
614
|
+
packageNames: new Set(plugins.flatMap((entry) => {
|
|
615
|
+
const { source: legacySource, sources } = objectValue(objectValue(entry).item);
|
|
616
|
+
return (Array.isArray(sources) ? sources : legacySource === undefined ? [] : [legacySource]).flatMap((source) => {
|
|
617
|
+
const { kind, packageName } = objectValue(source);
|
|
618
|
+
return kind === "package" && isString2(packageName) ? [packageName] : [];
|
|
619
|
+
});
|
|
715
620
|
})),
|
|
716
|
-
skillPaths:
|
|
717
|
-
const item = objectValue(entry);
|
|
718
|
-
return isString2(item.skillPath) ? [item.skillPath] : [];
|
|
719
|
-
}))
|
|
621
|
+
skillPaths: manifestSkillPaths(manifest)
|
|
720
622
|
};
|
|
721
623
|
});
|
|
624
|
+
function manifestSkillPaths(manifest) {
|
|
625
|
+
const materializedSkills = Array.isArray(manifest.materializedSkills) ? manifest.materializedSkills : [];
|
|
626
|
+
return new Set(materializedSkills.flatMap((entry) => {
|
|
627
|
+
const item = objectValue(entry);
|
|
628
|
+
return isString2(item.skillPath) ? [item.skillPath] : [];
|
|
629
|
+
}));
|
|
630
|
+
}
|
|
631
|
+
var readManifestSkillPaths = (manifestPath) => map3(readExistingText(manifestPath), (text) => manifestSkillPaths(parseJsonObject(text)));
|
|
722
632
|
var managedManifestPathForConfig = (paths, file) => gen(function* () {
|
|
723
633
|
const fs = yield* FileSystem;
|
|
724
634
|
const manifestRoot = managedManifestRootPath(paths, file);
|
|
@@ -778,13 +688,13 @@ function manifestFileForHarness(harness) {
|
|
|
778
688
|
return "agent-environment.manifest.json";
|
|
779
689
|
return `agent-environment.${harness}.manifest.json`;
|
|
780
690
|
}
|
|
781
|
-
function mergeOpenCodeConfig(existingText, generatedText, managedMcpNames,
|
|
691
|
+
function mergeOpenCodeConfig(existingText, generatedText, managedMcpNames, managedPackageNames) {
|
|
782
692
|
const existing = parseJsonObject(existingText);
|
|
783
693
|
const generated = parseJsonObject(generatedText);
|
|
784
694
|
const existingMcp = { ...objectValue(existing.mcp) };
|
|
785
695
|
for (const name of managedMcpNames)
|
|
786
696
|
delete existingMcp[name];
|
|
787
|
-
const existingPlugins = uniqueStrings(arrayValue(existing.plugin).filter((entry) => !
|
|
697
|
+
const existingPlugins = uniqueStrings(arrayValue(existing.plugin).filter((entry) => !isManagedPackageReference(entry, managedPackageNames)));
|
|
788
698
|
const merged = {
|
|
789
699
|
...existing,
|
|
790
700
|
...generated,
|
|
@@ -802,22 +712,25 @@ function mergeOpenCodeConfig(existingText, generatedText, managedMcpNames, manag
|
|
|
802
712
|
return `${JSON.stringify(merged, null, 2)}
|
|
803
713
|
`;
|
|
804
714
|
}
|
|
805
|
-
function
|
|
715
|
+
function isManagedPackageReference(entry, managedPackageNames, prefix = "") {
|
|
806
716
|
if (!isString2(entry))
|
|
807
717
|
return false;
|
|
808
|
-
for (const name of
|
|
809
|
-
if (entry === name || entry.startsWith(`${name}@`))
|
|
718
|
+
for (const name of managedPackageNames) {
|
|
719
|
+
if (entry === `${prefix}${name}` || entry.startsWith(`${prefix}${name}@`))
|
|
810
720
|
return true;
|
|
811
721
|
}
|
|
812
722
|
return false;
|
|
813
723
|
}
|
|
814
|
-
function mergePiSettingsConfig(existingText, generatedText) {
|
|
724
|
+
function mergePiSettingsConfig(existingText, generatedText, managedPackageNames) {
|
|
815
725
|
const existing = parseJsonObject(existingText);
|
|
816
726
|
const generated = parseJsonObject(generatedText);
|
|
817
727
|
const merged = {
|
|
818
728
|
...existing,
|
|
819
729
|
...generated,
|
|
820
|
-
packages: uniquePackageEntries([
|
|
730
|
+
packages: uniquePackageEntries([
|
|
731
|
+
...packageEntries(existing.packages).filter((entry) => !isManagedPackageReference(entry, managedPackageNames, "npm:")),
|
|
732
|
+
...packageEntries(generated.packages)
|
|
733
|
+
])
|
|
821
734
|
};
|
|
822
735
|
return `${JSON.stringify(merged, null, 2)}
|
|
823
736
|
`;
|
|
@@ -855,8 +768,8 @@ function isPiConfigPath(path, file, name) {
|
|
|
855
768
|
const parent = path.dirname(file);
|
|
856
769
|
return path.basename(file) === name && (path.basename(parent) === ".pi" || path.basename(parent) === "agent" && path.basename(path.dirname(parent)) === ".pi");
|
|
857
770
|
}
|
|
858
|
-
function mergeCodexConfig(existingText, generatedText, managedMcpNames, managedSkillPaths
|
|
859
|
-
const preserved = stripTomlTables(existingText, new Set([...managedMcpNames, ...mcpServerNames(generatedText)]), new Set([...managedSkillPaths, ...codexSkillPaths(generatedText)])).trimEnd();
|
|
771
|
+
function mergeCodexConfig(existingText, generatedText, managedMcpNames, managedSkillPaths, keepKitmanPlugins) {
|
|
772
|
+
const preserved = stripTomlTables(existingText, new Set([...managedMcpNames, ...mcpServerNames(generatedText)]), new Set([...managedSkillPaths, ...codexSkillPaths(generatedText)]), keepKitmanPlugins ? undefined : kitmanPluginNames(generatedText)).trimEnd();
|
|
860
773
|
const generated = generatedText.trim();
|
|
861
774
|
if (preserved.length === 0)
|
|
862
775
|
return `${generated}
|
|
@@ -869,13 +782,17 @@ function mergeCodexConfig(existingText, generatedText, managedMcpNames, managedS
|
|
|
869
782
|
${generated}
|
|
870
783
|
`;
|
|
871
784
|
}
|
|
872
|
-
function stripTomlTables(text, removeMcpServerNames, removeSkillPaths) {
|
|
785
|
+
function stripTomlTables(text, removeMcpServerNames, removeSkillPaths, keptKitmanPlugins) {
|
|
873
786
|
const lines = text.split(`
|
|
874
787
|
`);
|
|
788
|
+
const staleHookState = (key) => {
|
|
789
|
+
const plugin = /^([^@]+)@kitman:/u.exec(key ?? "")?.[1];
|
|
790
|
+
return keptKitmanPlugins !== undefined && plugin !== undefined && !keptKitmanPlugins.has(plugin);
|
|
791
|
+
};
|
|
875
792
|
const kept = [];
|
|
876
793
|
for (let index = 0;index < lines.length; ) {
|
|
877
794
|
const line = lines[index] ?? "";
|
|
878
|
-
const table =
|
|
795
|
+
const table = tomlTableHeader(line);
|
|
879
796
|
if (table === undefined) {
|
|
880
797
|
kept.push(line);
|
|
881
798
|
index += 1;
|
|
@@ -883,23 +800,65 @@ function stripTomlTables(text, removeMcpServerNames, removeSkillPaths) {
|
|
|
883
800
|
}
|
|
884
801
|
const block = [line];
|
|
885
802
|
index += 1;
|
|
886
|
-
while (index < lines.length &&
|
|
803
|
+
while (index < lines.length && tomlTableHeader(lines[index] ?? "") === undefined) {
|
|
887
804
|
block.push(lines[index] ?? "");
|
|
888
805
|
index += 1;
|
|
889
806
|
}
|
|
890
807
|
const skillPath = table === "skills.config" ? codexSkillPathFromBlock(block) : undefined;
|
|
891
|
-
const
|
|
892
|
-
|
|
808
|
+
const [first, second, hookKey, ...rest] = tomlKeyPath(table);
|
|
809
|
+
const hookState = first === "hooks" && second === "state";
|
|
810
|
+
const drop = keptKitmanPlugins !== undefined && isKitmanPluginTable([first ?? "", second ?? ""]) || hookState && rest.length === 0 && staleHookState(hookKey) || codexMcpServerName(table, removeMcpServerNames) !== undefined || skillPath !== undefined && removeSkillPaths.has(skillPath);
|
|
811
|
+
if (drop)
|
|
812
|
+
continue;
|
|
813
|
+
if (hookState && hookKey === undefined)
|
|
814
|
+
kept.push(...block.filter((entry) => !staleHookState(tomlKeyPath(/^([^=]+)=/u.exec(entry)?.[1] ?? "")[0])));
|
|
815
|
+
else
|
|
893
816
|
kept.push(...block);
|
|
894
817
|
}
|
|
895
818
|
return kept.join(`
|
|
896
819
|
`);
|
|
897
820
|
}
|
|
821
|
+
function tomlTableHeader(line) {
|
|
822
|
+
return /^\s*\[\[?((?:"(?:[^"\\]|\\.)*"|'[^']*'|[^"'[\]#])+)\]\]?\s*(?:#.*)?$/u.exec(line)?.[1];
|
|
823
|
+
}
|
|
824
|
+
function kitmanPluginNames(toml) {
|
|
825
|
+
return new Set(toml.split(`
|
|
826
|
+
`).flatMap((line) => {
|
|
827
|
+
const [table, name] = tomlKeyPath(tomlTableHeader(line) ?? "");
|
|
828
|
+
return table === "plugins" && name?.endsWith("@kitman") === true ? [name.slice(0, -"@kitman".length)] : [];
|
|
829
|
+
}));
|
|
830
|
+
}
|
|
831
|
+
function isKitmanPluginTable([table, name]) {
|
|
832
|
+
return table === "marketplaces" && name === "kitman" || table === "plugins" && name?.endsWith("@kitman") === true;
|
|
833
|
+
}
|
|
834
|
+
function tomlKeyPath(key) {
|
|
835
|
+
const part = /\s*(?:"((?:[^"\\]|\\.)*)"|'([^']*)'|([\w-]+))\s*(?:\.|$)/uy;
|
|
836
|
+
const parts = [];
|
|
837
|
+
while (part.lastIndex < key.length) {
|
|
838
|
+
const match = part.exec(key);
|
|
839
|
+
if (match === null)
|
|
840
|
+
return [];
|
|
841
|
+
parts.push(match[3] ?? match[2] ?? unescapeTomlString(match[1] ?? ""));
|
|
842
|
+
}
|
|
843
|
+
return parts;
|
|
844
|
+
}
|
|
845
|
+
function unescapeTomlString(text) {
|
|
846
|
+
const escapes = new Map([
|
|
847
|
+
["b", "\b"],
|
|
848
|
+
["t", "\t"],
|
|
849
|
+
["n", `
|
|
850
|
+
`],
|
|
851
|
+
["f", "\f"],
|
|
852
|
+
["r", "\r"],
|
|
853
|
+
["e", "\x1B"]
|
|
854
|
+
]);
|
|
855
|
+
return text.replace(/\\(?:u([\da-fA-F]{4})|U([\da-fA-F]{8})|(.))/gu, (_, short, long, char) => short !== undefined || long !== undefined ? String.fromCodePoint(Number.parseInt(short ?? long, 16)) : escapes.get(char) ?? char);
|
|
856
|
+
}
|
|
898
857
|
function mcpServerNames(toml) {
|
|
899
858
|
const names = new Set;
|
|
900
859
|
for (const line of toml.split(`
|
|
901
860
|
`)) {
|
|
902
|
-
const table =
|
|
861
|
+
const table = tomlTableHeader(line);
|
|
903
862
|
if (table === undefined)
|
|
904
863
|
continue;
|
|
905
864
|
const name = codexMcpServerTableName(table);
|
|
@@ -914,13 +873,13 @@ function codexSkillPaths(toml) {
|
|
|
914
873
|
`);
|
|
915
874
|
for (let index = 0;index < lines.length; ) {
|
|
916
875
|
const line = lines[index] ?? "";
|
|
917
|
-
if (
|
|
876
|
+
if (!/^\s*\[\[\s*skills\.config\s*\]\]/u.test(line)) {
|
|
918
877
|
index += 1;
|
|
919
878
|
continue;
|
|
920
879
|
}
|
|
921
880
|
const block = [line];
|
|
922
881
|
index += 1;
|
|
923
|
-
while (index < lines.length &&
|
|
882
|
+
while (index < lines.length && tomlTableHeader(lines[index] ?? "") === undefined) {
|
|
924
883
|
block.push(lines[index] ?? "");
|
|
925
884
|
index += 1;
|
|
926
885
|
}
|
|
@@ -961,6 +920,46 @@ function uniqueStrings(values) {
|
|
|
961
920
|
return [...new Set(values)];
|
|
962
921
|
}
|
|
963
922
|
|
|
923
|
+
// packages/agent-runtime/src/source-resolver.ts
|
|
924
|
+
import { tmpdir } from "os";
|
|
925
|
+
class SourceResolutionFailed extends TaggedError("SourceResolutionFailed") {
|
|
926
|
+
get message() {
|
|
927
|
+
return this.reason;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
var isCommitHash = (ref) => ref !== undefined && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(ref);
|
|
931
|
+
var DistTags = Struct({ latest: String2 });
|
|
932
|
+
var gitCommit = (url, ref) => gen(function* () {
|
|
933
|
+
const patterns = ref === undefined ? ["HEAD"] : [ref, `${ref}^{}`];
|
|
934
|
+
const output = yield* runCommand2("git", ["ls-remote", "--", url, ...patterns], {
|
|
935
|
+
cwd: tmpdir(),
|
|
936
|
+
timeout: "1 minute"
|
|
937
|
+
}).pipe(mapError((error) => new SourceResolutionFailed({ reason: error.message })));
|
|
938
|
+
const lines = output.split(`
|
|
939
|
+
`).map((line) => line.split("\t"));
|
|
940
|
+
const names = ref === undefined ? ["HEAD"] : ref.startsWith("refs/") ? [ref] : [`refs/heads/${ref}`, `refs/tags/${ref}`];
|
|
941
|
+
const commit = (lines.find(([, name]) => names.some((candidate) => name === `${candidate}^{}`)) ?? lines.find(([, name]) => names.includes(name ?? "")))?.[0];
|
|
942
|
+
if (!isCommitHash(commit))
|
|
943
|
+
return yield* new SourceResolutionFailed({ reason: `${url} has no ${ref ?? "default branch"}` });
|
|
944
|
+
return commit;
|
|
945
|
+
});
|
|
946
|
+
var npmLatest = (packageName) => get4(`https://registry.npmjs.org/-/package/${packageName.replace("/", "%2F")}/dist-tags`).pipe(flatMap2(filterStatusOk), flatMap2(schemaBodyJson(DistTags)), map3((tags) => tags.latest), provide3(layer4), mapError((error) => new SourceResolutionFailed({ reason: `npm has no latest ${packageName}: ${error.message}` })));
|
|
947
|
+
var SourceResolver = Reference("kitman/agent-runtime/SourceResolver", {
|
|
948
|
+
defaultValue: () => ({ gitCommit, npmLatest })
|
|
949
|
+
});
|
|
950
|
+
var withSourceResolutionCache = (effect) => gen(function* () {
|
|
951
|
+
const resolver = yield* SourceResolver;
|
|
952
|
+
const answers = new Map;
|
|
953
|
+
const once = (key, resolve) => suspend(() => answers.get(key) ?? resolve.pipe(exit, flatMap2((exit) => {
|
|
954
|
+
answers.set(key, exit);
|
|
955
|
+
return exit;
|
|
956
|
+
})));
|
|
957
|
+
return yield* effect.pipe(provideService(SourceResolver, {
|
|
958
|
+
gitCommit: (url, ref) => once(`git:${url}#${ref ?? ""}`, resolver.gitCommit(url, ref)),
|
|
959
|
+
npmLatest: (packageName) => once(`npm:${packageName}`, resolver.npmLatest(packageName))
|
|
960
|
+
}));
|
|
961
|
+
});
|
|
962
|
+
|
|
964
963
|
// packages/agent-runtime/src/config-writer.ts
|
|
965
964
|
var Sources = {
|
|
966
965
|
opencodeMcp: "https://opencode.ai/docs/mcp-servers/",
|
|
@@ -1005,16 +1004,17 @@ var previewAgentEnvironmentConfigSync = fnUntraced(function* (previewInput) {
|
|
|
1005
1004
|
const syncKitPath = input.syncKit === undefined ? undefined : findForbiddenAgentEnvironmentSecretPath(input.syncKit);
|
|
1006
1005
|
if (syncKitPath !== undefined)
|
|
1007
1006
|
plan.issues.push({ message: `Sync Kit contains forbidden secret-like value at ${syncKitPath}` });
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1007
|
+
const context = { ...input, ...yield* resolvePackagePlugins(input, plan) };
|
|
1008
|
+
if (context.environment.harness === "opencode")
|
|
1009
|
+
renderOpenCode(context, plan);
|
|
1010
|
+
else if (context.environment.harness === "codex")
|
|
1011
|
+
renderCodex(context, plan);
|
|
1012
|
+
else if (context.environment.harness === "claude-code")
|
|
1013
|
+
renderClaudeCode(context, plan);
|
|
1014
|
+
else if (context.environment.harness === "pi")
|
|
1015
|
+
renderPi(context, plan);
|
|
1016
|
+
renderSkillFiles(context, plan);
|
|
1017
|
+
renderManifest(context, plan);
|
|
1018
1018
|
if (plan.operations.some((operation) => isUnsafeConfigWritePath(operation.path))) {
|
|
1019
1019
|
plan.issues.push({ message: "AgentEnvironment config sync produced an unsafe relative write path." });
|
|
1020
1020
|
}
|
|
@@ -1027,13 +1027,53 @@ var previewAgentEnvironmentConfigSync = fnUntraced(function* (previewInput) {
|
|
|
1027
1027
|
...input.syncKit === undefined ? undefined : { syncKitHash: input.syncKit.syncKitHash, kitRefs: input.syncKit.kitRefs }
|
|
1028
1028
|
};
|
|
1029
1029
|
});
|
|
1030
|
+
var resolvePackagePlugins = (input, plan) => gen(function* () {
|
|
1031
|
+
const harness = input.environment.harness;
|
|
1032
|
+
if (input.noInstall === true || harness !== "opencode" && harness !== "pi")
|
|
1033
|
+
return { environment: input.environment, unresolvedPlugins: [] };
|
|
1034
|
+
const resolver = yield* SourceResolver;
|
|
1035
|
+
const unresolvedPlugins = [];
|
|
1036
|
+
const plugins = yield* forEach(input.environment.plugins, (plugin) => {
|
|
1037
|
+
const source = packageSource(plugin);
|
|
1038
|
+
if (source === undefined || source.version !== undefined && input.versions !== "latest")
|
|
1039
|
+
return succeed5([plugin]);
|
|
1040
|
+
return resolver.npmLatest(source.packageName).pipe(map3((version) => [
|
|
1041
|
+
{
|
|
1042
|
+
...plugin,
|
|
1043
|
+
item: {
|
|
1044
|
+
...plugin.item,
|
|
1045
|
+
sources: [
|
|
1046
|
+
{ ...source, version },
|
|
1047
|
+
...plugin.item.sources.filter((candidate) => candidate !== source)
|
|
1048
|
+
]
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
]), catch_((error) => sync(() => {
|
|
1052
|
+
const issue = {
|
|
1053
|
+
message: `Kitman could not resolve plugin ${plugin.item.name} from npm package ${source.packageName}: ${error.message}`,
|
|
1054
|
+
source: sourceLabel2(plugin.source)
|
|
1055
|
+
};
|
|
1056
|
+
if (plugin.item.policy === "required")
|
|
1057
|
+
plan.issues.push(issue);
|
|
1058
|
+
else {
|
|
1059
|
+
plan.warnings.push(issue);
|
|
1060
|
+
unresolvedPlugins.push(plugin);
|
|
1061
|
+
}
|
|
1062
|
+
return [];
|
|
1063
|
+
})));
|
|
1064
|
+
});
|
|
1065
|
+
return { environment: { ...input.environment, plugins: plugins.flat() }, unresolvedPlugins };
|
|
1066
|
+
});
|
|
1030
1067
|
var applyAgentEnvironmentConfigSync = fnUntraced(function* (plan) {
|
|
1031
1068
|
if (plan.operations.some((operation) => isUnsafeConfigWritePath(operation.path)))
|
|
1032
1069
|
return yield* new AgentEnvironmentConfigSyncError({
|
|
1033
1070
|
issues: [{ message: "AgentEnvironment config apply rejected an unsafe relative write path." }]
|
|
1034
1071
|
});
|
|
1035
1072
|
for (const operation of plan.operations)
|
|
1036
|
-
yield* writeAgentEnvironmentConfigFile(operation.path, operation.content
|
|
1073
|
+
yield* writeAgentEnvironmentConfigFile(operation.path, operation.content, {
|
|
1074
|
+
kitmanPlugins: operation.keepKitmanPlugins === true,
|
|
1075
|
+
...operation.keepPackages === undefined ? undefined : { packages: operation.keepPackages }
|
|
1076
|
+
});
|
|
1037
1077
|
return plan;
|
|
1038
1078
|
});
|
|
1039
1079
|
var SafeModelReference = /^(?!.*(?:https?:\/\/|ftp:\/\/|file:\/\/|Bearer\s|secret|token|password))(?!.*(?:^|[\s])(?:\/|~\/|[A-Za-z]:\\\\))[A-Za-z0-9][A-Za-z0-9_.:/@-]{0,239}$/iu;
|
|
@@ -1096,6 +1136,7 @@ function renderOpenCode(input, plan) {
|
|
|
1096
1136
|
kind: "write_file",
|
|
1097
1137
|
path: targetPath(input, "opencode.json"),
|
|
1098
1138
|
description: "OpenCode project config for Kitman AgentEnvironment MCP servers and package plugins.",
|
|
1139
|
+
...keepUnresolvedPackages(input),
|
|
1099
1140
|
content: json({
|
|
1100
1141
|
$schema: "https://opencode.ai/config.json",
|
|
1101
1142
|
...model === undefined ? undefined : { model },
|
|
@@ -1115,14 +1156,18 @@ function renderCodex(input, plan) {
|
|
|
1115
1156
|
if (block !== undefined)
|
|
1116
1157
|
serverBlocks.push(block);
|
|
1117
1158
|
}
|
|
1118
|
-
|
|
1159
|
+
const materializedPlugins = input.materializedPlugins ?? [];
|
|
1160
|
+
const pluginBlocks = renderCodexPlugins(materializedPlugins);
|
|
1161
|
+
for (const plugin of unhandledPlugins(input))
|
|
1162
|
+
reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, input.environment.harness, input.scope !== "user" && plugin.item.sources.some((source) => source.kind === "git") ? "Codex git plugins install only at user scope" : unsupportedPluginReason(plugin), sourceLabel2(plugin.source));
|
|
1119
1163
|
renderCodexInstructions(input, plan);
|
|
1120
1164
|
plan.operations.push({
|
|
1121
1165
|
kind: "write_file",
|
|
1122
1166
|
path: targetPath(input, ".codex/config.toml"),
|
|
1123
1167
|
description: "Codex project config.toml MCP server entries for Kitman AgentEnvironment.",
|
|
1124
|
-
|
|
1125
|
-
|
|
1168
|
+
...(input.skippedPlugins ?? []).length === 0 ? undefined : { keepKitmanPlugins: true },
|
|
1169
|
+
content: serverBlocks.length === 0 && pluginBlocks.length === 0 && model === undefined ? `# Kitman Codex MCP config generated by kitman sync
|
|
1170
|
+
` : `${[...model === undefined ? [] : [`model = ${tomlString(model)}`], ...serverBlocks, ...pluginBlocks].join(`
|
|
1126
1171
|
|
|
1127
1172
|
`)}
|
|
1128
1173
|
`,
|
|
@@ -1150,7 +1195,7 @@ function renderCodexInstructions(input, plan) {
|
|
|
1150
1195
|
"",
|
|
1151
1196
|
"## Plugins",
|
|
1152
1197
|
"",
|
|
1153
|
-
...markdownList(input.environment.plugins.map((entry) => `${entry.item.name} (${entry.item.policy}, ${entry
|
|
1198
|
+
...markdownList(input.environment.plugins.map((entry) => `${entry.item.name} (${entry.item.policy}, ${sourceKinds(entry, ", ")})`)),
|
|
1154
1199
|
"",
|
|
1155
1200
|
"## Harness Settings",
|
|
1156
1201
|
"",
|
|
@@ -1193,7 +1238,7 @@ function renderCodexInstructions(input, plan) {
|
|
|
1193
1238
|
}
|
|
1194
1239
|
function renderClaudeCode(input, plan) {
|
|
1195
1240
|
const mcpServers = objectMap(input.environment.mcpServers, (server) => renderClaudeMcpServer(input, server, plan));
|
|
1196
|
-
renderUnsupportedPlugins(input
|
|
1241
|
+
renderUnsupportedPlugins(unhandledPlugins(input), input.environment.harness, plan);
|
|
1197
1242
|
const model = renderModelSetting(input, plan);
|
|
1198
1243
|
const skillOverrides = Object.fromEntries((input.materializedSkills ?? []).map((skill) => [skill.name, "on"]));
|
|
1199
1244
|
if (Object.keys(mcpServers).length > 0 || Object.keys(skillOverrides).length > 0) {
|
|
@@ -1226,18 +1271,17 @@ function renderPi(input, plan) {
|
|
|
1226
1271
|
...Object.keys(mcpServers).length === 0 ? [] : ["npm:pi-mcp-adapter"],
|
|
1227
1272
|
...renderPiPlugins(input.environment.plugins, plan)
|
|
1228
1273
|
];
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
}
|
|
1274
|
+
plan.operations.push({
|
|
1275
|
+
kind: "write_file",
|
|
1276
|
+
path: targetPath(input, ".pi/settings.json"),
|
|
1277
|
+
description: "Pi project settings generated from resolved non-secret MCP and model intent.",
|
|
1278
|
+
...keepUnresolvedPackages(input),
|
|
1279
|
+
content: json({
|
|
1280
|
+
...packages.length === 0 ? undefined : { packages },
|
|
1281
|
+
...model === undefined ? undefined : { model }
|
|
1282
|
+
}),
|
|
1283
|
+
sources: [Sources.piReadme, Sources.piMcpAdapter]
|
|
1284
|
+
});
|
|
1241
1285
|
plan.operations.push({
|
|
1242
1286
|
kind: "write_file",
|
|
1243
1287
|
path: targetPath(input, ".pi/mcp.json"),
|
|
@@ -1249,8 +1293,9 @@ function renderPi(input, plan) {
|
|
|
1249
1293
|
function renderPiPlugins(plugins, plan) {
|
|
1250
1294
|
const packages = [];
|
|
1251
1295
|
for (const plugin of plugins) {
|
|
1252
|
-
|
|
1253
|
-
|
|
1296
|
+
const source = packageSource(plugin);
|
|
1297
|
+
if (source) {
|
|
1298
|
+
packages.push(`npm:${packageReference(source.packageName, source.version)}`);
|
|
1254
1299
|
continue;
|
|
1255
1300
|
}
|
|
1256
1301
|
reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, "pi", "Pi package plugins require an npm package source", sourceLabel2(plugin.source));
|
|
@@ -1373,20 +1418,52 @@ function hasNameArgument(args) {
|
|
|
1373
1418
|
function renderOpenCodePlugins(plugins, plan) {
|
|
1374
1419
|
const packages = [];
|
|
1375
1420
|
for (const plugin of plugins) {
|
|
1376
|
-
|
|
1377
|
-
|
|
1421
|
+
const source = packageSource(plugin);
|
|
1422
|
+
if (source) {
|
|
1423
|
+
packages.push(packageReference(source.packageName, source.version));
|
|
1378
1424
|
continue;
|
|
1379
1425
|
}
|
|
1380
1426
|
reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, "opencode", "this writer only has source-verified OpenCode plugin config for package sources", sourceLabel2(plugin.source));
|
|
1381
1427
|
}
|
|
1382
1428
|
return packages;
|
|
1383
1429
|
}
|
|
1430
|
+
function keepUnresolvedPackages(input) {
|
|
1431
|
+
const packages = (input.unresolvedPlugins ?? []).flatMap((plugin) => packageSource(plugin)?.packageName ?? []);
|
|
1432
|
+
return packages.length === 0 ? undefined : { keepPackages: packages };
|
|
1433
|
+
}
|
|
1434
|
+
function unhandledPlugins(input) {
|
|
1435
|
+
const handled = new Set([
|
|
1436
|
+
...(input.materializedPlugins ?? []).map((plugin) => plugin.name),
|
|
1437
|
+
...input.skippedPlugins ?? []
|
|
1438
|
+
]);
|
|
1439
|
+
return input.environment.plugins.filter((plugin) => !handled.has(plugin.item.name));
|
|
1440
|
+
}
|
|
1441
|
+
function renderCodexPlugins(plugins) {
|
|
1442
|
+
const marketplaceRoot = plugins[0]?.marketplaceRoot;
|
|
1443
|
+
if (marketplaceRoot === undefined)
|
|
1444
|
+
return [];
|
|
1445
|
+
return [
|
|
1446
|
+
`[marketplaces.kitman]
|
|
1447
|
+
source_type = "local"
|
|
1448
|
+
source = ${tomlString(marketplaceRoot)}`,
|
|
1449
|
+
...plugins.map((plugin) => `[plugins.${tomlString(`${plugin.name}@kitman`)}]
|
|
1450
|
+
enabled = true`)
|
|
1451
|
+
];
|
|
1452
|
+
}
|
|
1384
1453
|
function renderUnsupportedPlugins(plugins, harness, plan) {
|
|
1385
1454
|
for (const plugin of plugins) {
|
|
1386
|
-
|
|
1387
|
-
reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, harness, `this writer has no source-verified plugin installer for ${sourceKind}`, sourceLabel2(plugin.source));
|
|
1455
|
+
reportUnsupported(plan, plugin.item.policy, `plugin ${plugin.item.name}`, harness, unsupportedPluginReason(plugin), sourceLabel2(plugin.source));
|
|
1388
1456
|
}
|
|
1389
1457
|
}
|
|
1458
|
+
function unsupportedPluginReason(plugin) {
|
|
1459
|
+
return `this writer has no source-verified plugin installer for ${sourceKinds(plugin, " or ")} sources`;
|
|
1460
|
+
}
|
|
1461
|
+
function packageSource(plugin) {
|
|
1462
|
+
return plugin.item.sources.find((source) => source.kind === "package");
|
|
1463
|
+
}
|
|
1464
|
+
function sourceKinds(plugin, separator) {
|
|
1465
|
+
return [...new Set(plugin.item.sources.map((source) => source.kind))].join(separator);
|
|
1466
|
+
}
|
|
1390
1467
|
function renderSkillFiles(input, plan) {
|
|
1391
1468
|
const materializedSkillNames = new Set((input.materializedSkills ?? []).map((skill) => skill.name));
|
|
1392
1469
|
for (const skill of input.environment.skills) {
|
|
@@ -1435,7 +1512,7 @@ function renderManifest(input, plan) {
|
|
|
1435
1512
|
generatedBy: "kitman",
|
|
1436
1513
|
harness: input.environment.harness,
|
|
1437
1514
|
skills: input.environment.skills,
|
|
1438
|
-
plugins: input.environment.plugins,
|
|
1515
|
+
plugins: [...input.environment.plugins, ...input.unresolvedPlugins ?? []],
|
|
1439
1516
|
mcpServers: input.environment.mcpServers,
|
|
1440
1517
|
harnessSettings: input.environment.harnessSettings,
|
|
1441
1518
|
providerAuthRefs: input.environment.providerAuthRefs,
|
|
@@ -1719,8 +1796,36 @@ function repositoryUrl(repoBinding) {
|
|
|
1719
1796
|
return repoBinding.repositoryUrl ?? `https://${repoBinding.host}/${repoBinding.owner}/${repoBinding.name}.git`;
|
|
1720
1797
|
}
|
|
1721
1798
|
// packages/agent-runtime/src/skill-materializer.ts
|
|
1722
|
-
var gitSourceKey = (source) => `${source.url}#${source.ref}:${source.path ?? ""}`;
|
|
1723
|
-
var
|
|
1799
|
+
var gitSourceKey = (source) => `${source.url}#${source.ref ?? ""}:${source.path ?? ""}`;
|
|
1800
|
+
var approvedCommits = (approved, source) => approved.flatMap((entry) => {
|
|
1801
|
+
const [commit, path] = entry.startsWith(`${source.url}#`) ? entry.slice(source.url.length + 1).split(/:(.*)/su) : [];
|
|
1802
|
+
return isCommitHash(commit) && path === (source.path ?? "") ? [commit] : [];
|
|
1803
|
+
});
|
|
1804
|
+
var resolveGitSource = (subject, source, input) => gen(function* () {
|
|
1805
|
+
if (!source.url.startsWith("https://"))
|
|
1806
|
+
return { problem: `${subject} must come from an https URL, not ${source.url}.` };
|
|
1807
|
+
const resolver = yield* SourceResolver;
|
|
1808
|
+
const latest = input.versions === "latest";
|
|
1809
|
+
const ref = latest ? undefined : source.ref;
|
|
1810
|
+
const resolved = (isCommitHash(ref) ? succeed5(ref) : resolver.gitCommit(source.url, ref)).pipe(map3((commit) => ({ commit })), catch_((error) => succeed5({
|
|
1811
|
+
problem: `Kitman could not resolve ${subject} from ${source.url}${ref === undefined ? "" : ` at ${ref}`}: ${error.message}`
|
|
1812
|
+
})));
|
|
1813
|
+
if (input.requireReviewedSources !== true)
|
|
1814
|
+
return yield* resolved;
|
|
1815
|
+
const approved = approvedCommits(input.approvedSkillSources ?? [], source);
|
|
1816
|
+
const wanted = isCommitHash(ref) ? { commit: ref } : undefined;
|
|
1817
|
+
if (ref === undefined && !latest && approved.length > 0)
|
|
1818
|
+
return { commit: approved.at(-1) ?? "" };
|
|
1819
|
+
const candidate = wanted ?? (yield* resolved);
|
|
1820
|
+
if ("problem" in candidate)
|
|
1821
|
+
return { rejected: true, problem: `${candidate.problem} Under review Kitman keeps no copy it cannot check.` };
|
|
1822
|
+
if (approved.includes(candidate.commit))
|
|
1823
|
+
return candidate;
|
|
1824
|
+
return {
|
|
1825
|
+
rejected: true,
|
|
1826
|
+
problem: `${subject} is not approved at ${candidate.commit}; approve it by adding ${source.url}#${candidate.commit}:${source.path ?? ""} to approvedSkillSources in your Kitman user config, or remove it from the Kit.`
|
|
1827
|
+
};
|
|
1828
|
+
});
|
|
1724
1829
|
|
|
1725
1830
|
class AgentEnvironmentMaterializationError extends TaggedError("AgentEnvironmentMaterializationError") {
|
|
1726
1831
|
get message() {
|
|
@@ -1731,6 +1836,7 @@ class AgentEnvironmentMaterializationError extends TaggedError("AgentEnvironment
|
|
|
1731
1836
|
class SkillMaterializationRefused extends TaggedError("SkillMaterializationRefused") {
|
|
1732
1837
|
}
|
|
1733
1838
|
var previewAgentEnvironmentMaterialization = fnUntraced(function* (input) {
|
|
1839
|
+
const fs = yield* FileSystem;
|
|
1734
1840
|
const path = yield* Path;
|
|
1735
1841
|
const home = input.rootDir ?? (yield* optionalEnv("HOME")) ?? ".";
|
|
1736
1842
|
const harness = input.environment.harness;
|
|
@@ -1738,43 +1844,70 @@ var previewAgentEnvironmentMaterialization = fnUntraced(function* (input) {
|
|
|
1738
1844
|
const targetRoot = skillBasePath2(path, harness, input.scope, input.rootDir, home);
|
|
1739
1845
|
const operations = [];
|
|
1740
1846
|
const materializedSkills = [];
|
|
1847
|
+
const materializedPlugins = [];
|
|
1848
|
+
const skippedPlugins = [];
|
|
1741
1849
|
const warnings = [];
|
|
1850
|
+
const unresolvedLinks = [];
|
|
1742
1851
|
for (const skill of input.environment.skills) {
|
|
1743
1852
|
const skillKey = skillIdentity(skill.item);
|
|
1853
|
+
const targetPath = path.join(targetRoot, `${skillSlug2(skill.item.name)}-${hashSegment(skillKey)}`);
|
|
1744
1854
|
const source = skill.item.source;
|
|
1745
1855
|
const fromKit = skill.source.label === undefined ? "" : ` (from Kit ${skill.source.label})`;
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1856
|
+
const gitSkillPath = path.join(sourceRoot, "git", hashSegment(skillKey), skillSlug2(skill.item.name));
|
|
1857
|
+
const problem = source.kind === "kitman" ? source.contentMarkdown === undefined ? `Kitman skill ${skill.item.name} has no contentMarkdown to materialize.` : undefined : input.noInstall === true ? `Skipping materialization for ${skill.item.name} because sync was run with noInstall.` : source.kind !== "git" ? `Skill ${skill.item.name}${fromKit} comes from ${source.kind}, which Kitman cannot install yet; only Kitman and git skills are installed. Remove it from the Kit or make it recommended.` : undefined;
|
|
1858
|
+
const resolution = problem === undefined && source.kind === "git" ? yield* resolveGitSource(`Git skill ${skill.item.name}${fromKit}`, source, input) : undefined;
|
|
1859
|
+
const issueMessage = problem ?? (resolution !== undefined && "problem" in resolution ? resolution.problem : undefined);
|
|
1860
|
+
if (issueMessage !== undefined) {
|
|
1861
|
+
const issue = { message: issueMessage, skillName: skill.item.name, skillKey };
|
|
1749
1862
|
if (skill.item.policy === "required")
|
|
1750
1863
|
return yield* new AgentEnvironmentMaterializationError({ issues: [issue] });
|
|
1751
1864
|
warnings.push(issue);
|
|
1865
|
+
if (resolution !== undefined && "rejected" in resolution)
|
|
1866
|
+
continue;
|
|
1867
|
+
unresolvedLinks.push(targetPath);
|
|
1868
|
+
if (source.kind === "git" && (yield* fs.exists(gitSkillPath).pipe(orElseSucceed(() => false))))
|
|
1869
|
+
materializedSkills.push({
|
|
1870
|
+
name: skill.item.name,
|
|
1871
|
+
key: skillKey,
|
|
1872
|
+
harness,
|
|
1873
|
+
source,
|
|
1874
|
+
skillPath: gitSkillPath,
|
|
1875
|
+
managed: true
|
|
1876
|
+
});
|
|
1752
1877
|
continue;
|
|
1753
1878
|
}
|
|
1879
|
+
const commit = resolution !== undefined && "commit" in resolution ? resolution.commit : undefined;
|
|
1754
1880
|
const expose = (skillPath) => {
|
|
1755
1881
|
operations.push({
|
|
1756
1882
|
kind: "symlink_skill",
|
|
1757
1883
|
skillName: skill.item.name,
|
|
1758
1884
|
skillKey,
|
|
1759
1885
|
sourcePath: skillPath,
|
|
1760
|
-
targetPath
|
|
1886
|
+
targetPath,
|
|
1761
1887
|
sourceRoot,
|
|
1762
1888
|
targetRoot
|
|
1763
1889
|
});
|
|
1764
|
-
materializedSkills.push({
|
|
1890
|
+
materializedSkills.push({
|
|
1891
|
+
name: skill.item.name,
|
|
1892
|
+
key: skillKey,
|
|
1893
|
+
harness,
|
|
1894
|
+
source,
|
|
1895
|
+
skillPath,
|
|
1896
|
+
managed: true,
|
|
1897
|
+
...commit === undefined ? undefined : { commit }
|
|
1898
|
+
});
|
|
1765
1899
|
};
|
|
1766
|
-
if (source.kind === "git") {
|
|
1767
|
-
const skillPath = path.join(sourceRoot, "git", hashSegment(skillKey), skillSlug2(skill.item.name));
|
|
1900
|
+
if (source.kind === "git" && commit !== undefined) {
|
|
1768
1901
|
operations.push({
|
|
1769
1902
|
kind: "fetch_git_skill",
|
|
1770
1903
|
skillName: skill.item.name,
|
|
1771
1904
|
skillKey,
|
|
1772
1905
|
url: source.url,
|
|
1773
|
-
ref:
|
|
1906
|
+
ref: commit,
|
|
1774
1907
|
...source.path === undefined ? undefined : { repositoryPath: source.path },
|
|
1775
|
-
path:
|
|
1908
|
+
path: gitSkillPath
|
|
1776
1909
|
});
|
|
1777
|
-
expose(
|
|
1910
|
+
expose(gitSkillPath);
|
|
1778
1911
|
} else if (source.kind === "kitman" && source.contentMarkdown !== undefined) {
|
|
1779
1912
|
const skillPath = path.join(sourceRoot, "kitman", hashSegment(source.skillId ?? source.name ?? skill.item.name), skillSlug2(skill.item.name));
|
|
1780
1913
|
operations.push({
|
|
@@ -1787,26 +1920,164 @@ var previewAgentEnvironmentMaterialization = fnUntraced(function* (input) {
|
|
|
1787
1920
|
expose(skillPath);
|
|
1788
1921
|
}
|
|
1789
1922
|
}
|
|
1790
|
-
|
|
1923
|
+
const codexMarketplace = harness === "codex" && input.scope === "user";
|
|
1924
|
+
const marketplaceRoot = path.join(sourceRoot, "codex-marketplace");
|
|
1925
|
+
const claudePluginsRoot = path.join(sourceRoot, "git-plugins");
|
|
1926
|
+
for (const plugin of harness === "claude-code" || codexMarketplace ? input.environment.plugins : []) {
|
|
1927
|
+
const source = plugin.item.sources.find((candidate) => candidate.kind === "git");
|
|
1928
|
+
if (source === undefined)
|
|
1929
|
+
continue;
|
|
1930
|
+
const name = plugin.item.name;
|
|
1931
|
+
const pluginKey = `plugin:git:${gitSourceKey(source)}:${name}`;
|
|
1932
|
+
const fromKit = plugin.source.label === undefined ? "" : ` (from Kit ${plugin.source.label})`;
|
|
1933
|
+
if (input.noInstall === true) {
|
|
1934
|
+
skippedPlugins.push(name);
|
|
1935
|
+
warnings.push({
|
|
1936
|
+
message: `Skipping materialization for plugin ${name} because sync was run with noInstall.`,
|
|
1937
|
+
skillName: name,
|
|
1938
|
+
skillKey: pluginKey
|
|
1939
|
+
});
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
const directoryName = `${skillSlug2(name)}-${hashSegment(pluginKey)}`;
|
|
1943
|
+
const [pluginPath, manifest] = harness === "codex" ? [path.join(marketplaceRoot, "plugins", directoryName), ".codex-plugin/plugin.json"] : [path.join(claudePluginsRoot, hashSegment(pluginKey), skillSlug2(name)), ".claude-plugin/plugin.json"];
|
|
1944
|
+
const resolution = yield* resolveGitSource(`Git plugin ${name}${fromKit}`, source, input);
|
|
1945
|
+
if ("problem" in resolution) {
|
|
1946
|
+
const issue = { message: resolution.problem, skillName: name, skillKey: pluginKey };
|
|
1947
|
+
if (plugin.item.policy === "required")
|
|
1948
|
+
return yield* new AgentEnvironmentMaterializationError({ issues: [issue] });
|
|
1949
|
+
warnings.push(issue);
|
|
1950
|
+
if ("rejected" in resolution)
|
|
1951
|
+
continue;
|
|
1952
|
+
unresolvedLinks.push(path.join(targetRoot, directoryName));
|
|
1953
|
+
if (yield* fs.exists(path.join(pluginPath, manifest)).pipe(orElseSucceed(() => false)))
|
|
1954
|
+
materializedPlugins.push({
|
|
1955
|
+
name,
|
|
1956
|
+
key: pluginKey,
|
|
1957
|
+
harness,
|
|
1958
|
+
source,
|
|
1959
|
+
pluginPath,
|
|
1960
|
+
...harness === "codex" ? { marketplaceRoot } : undefined
|
|
1961
|
+
});
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
operations.push({
|
|
1965
|
+
kind: "fetch_git_plugin",
|
|
1966
|
+
pluginName: name,
|
|
1967
|
+
pluginKey,
|
|
1968
|
+
url: source.url,
|
|
1969
|
+
ref: resolution.commit,
|
|
1970
|
+
manifest,
|
|
1971
|
+
path: pluginPath,
|
|
1972
|
+
sourceRoot
|
|
1973
|
+
});
|
|
1974
|
+
if (harness === "codex") {
|
|
1975
|
+
materializedPlugins.push({
|
|
1976
|
+
name,
|
|
1977
|
+
key: pluginKey,
|
|
1978
|
+
harness,
|
|
1979
|
+
source,
|
|
1980
|
+
pluginPath,
|
|
1981
|
+
marketplaceRoot,
|
|
1982
|
+
commit: resolution.commit
|
|
1983
|
+
});
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
1986
|
+
operations.push({
|
|
1987
|
+
kind: "symlink_skill",
|
|
1988
|
+
skillName: name,
|
|
1989
|
+
skillKey: pluginKey,
|
|
1990
|
+
sourcePath: pluginPath,
|
|
1991
|
+
targetPath: path.join(targetRoot, directoryName),
|
|
1992
|
+
sourceRoot,
|
|
1993
|
+
targetRoot
|
|
1994
|
+
});
|
|
1995
|
+
materializedPlugins.push({ name, key: pluginKey, harness, source, pluginPath, commit: resolution.commit });
|
|
1996
|
+
}
|
|
1997
|
+
if (input.noInstall !== true)
|
|
1998
|
+
operations.push({
|
|
1999
|
+
kind: "unlink_stale_links",
|
|
2000
|
+
path: targetRoot,
|
|
2001
|
+
sourceRoot,
|
|
2002
|
+
keepPaths: [
|
|
2003
|
+
...operations.flatMap((operation) => operation.kind === "symlink_skill" ? [operation.targetPath] : []),
|
|
2004
|
+
...unresolvedLinks
|
|
2005
|
+
],
|
|
2006
|
+
sharedWith: AgentHarnessIds.filter((other) => other !== harness && skillBasePath2(path, other, input.scope, input.rootDir, home) === targetRoot).map((other) => ({
|
|
2007
|
+
harness: other,
|
|
2008
|
+
manifestPath: path.join(input.scope === "user" ? home : input.rootDir ?? ".", ".kitman", `agent-environment.${other}.manifest.json`)
|
|
2009
|
+
}))
|
|
2010
|
+
});
|
|
2011
|
+
const marketplacePath = path.join(marketplaceRoot, ".agents", "plugins", "marketplace.json");
|
|
2012
|
+
if (codexMarketplace && input.noInstall !== true)
|
|
2013
|
+
operations.push({
|
|
2014
|
+
kind: "write_codex_marketplace",
|
|
2015
|
+
path: marketplacePath,
|
|
2016
|
+
content: `${yield* encodePrettyJsonString({
|
|
2017
|
+
name: "kitman",
|
|
2018
|
+
interface: { displayName: "Kitman" },
|
|
2019
|
+
plugins: materializedPlugins.map((plugin) => ({
|
|
2020
|
+
name: plugin.name,
|
|
2021
|
+
source: { source: "local", path: `./${path.relative(marketplaceRoot, plugin.pluginPath)}` },
|
|
2022
|
+
policy: { installation: "INSTALLED_BY_DEFAULT", authentication: "ON_INSTALL" },
|
|
2023
|
+
category: "Productivity"
|
|
2024
|
+
}))
|
|
2025
|
+
}).pipe(orDie)}
|
|
2026
|
+
`,
|
|
2027
|
+
sourceRoot
|
|
2028
|
+
});
|
|
2029
|
+
return {
|
|
2030
|
+
harness,
|
|
2031
|
+
operations,
|
|
2032
|
+
materializedSkills,
|
|
2033
|
+
materializedPlugins,
|
|
2034
|
+
skippedPlugins,
|
|
2035
|
+
warnings,
|
|
2036
|
+
...codexMarketplace && materializedPlugins.length > 0 ? { codexHookTrust: { home, marketplacePath } } : undefined
|
|
2037
|
+
};
|
|
2038
|
+
});
|
|
2039
|
+
var gitCheckouts = gen(function* () {
|
|
2040
|
+
const scope = yield* Scope;
|
|
2041
|
+
const trees = new Map;
|
|
2042
|
+
const checkout = (url, ref) => gen(function* () {
|
|
2043
|
+
const key = `${url}#${ref}`;
|
|
2044
|
+
const known = trees.get(key);
|
|
2045
|
+
if (known !== undefined)
|
|
2046
|
+
return known;
|
|
2047
|
+
const tree = yield* checkoutGitTree(url, ref).pipe(provide(scope));
|
|
2048
|
+
trees.set(key, tree);
|
|
2049
|
+
return tree;
|
|
2050
|
+
});
|
|
2051
|
+
return checkout;
|
|
1791
2052
|
});
|
|
1792
|
-
var applyAgentEnvironmentMaterialization =
|
|
2053
|
+
var applyAgentEnvironmentMaterialization = (plan, checkout) => gen(function* () {
|
|
2054
|
+
const checkoutTree = checkout ?? (yield* gitCheckouts);
|
|
1793
2055
|
for (const operation of plan.operations) {
|
|
1794
2056
|
if (operation.kind === "write_skill")
|
|
1795
2057
|
yield* writeSkill(plan, operation);
|
|
1796
2058
|
else if (operation.kind === "fetch_git_skill")
|
|
1797
|
-
yield* fetchGitSkill(plan, operation);
|
|
2059
|
+
yield* fetchGitSkill(plan, operation, checkoutTree);
|
|
2060
|
+
else if (operation.kind === "fetch_git_plugin")
|
|
2061
|
+
yield* fetchGitPlugin(operation, checkoutTree);
|
|
2062
|
+
else if (operation.kind === "write_codex_marketplace")
|
|
2063
|
+
yield* writeCodexMarketplace(operation);
|
|
2064
|
+
else if (operation.kind === "unlink_stale_links")
|
|
2065
|
+
yield* unlinkStaleLinks(operation);
|
|
1798
2066
|
else
|
|
1799
2067
|
yield* exposeSkill(operation);
|
|
1800
2068
|
}
|
|
1801
2069
|
return plan;
|
|
1802
|
-
});
|
|
2070
|
+
}).pipe(scoped);
|
|
1803
2071
|
var refuse = (message) => fail4(new SkillMaterializationRefused({ message }));
|
|
1804
2072
|
var managedSkillPath = (plan, skillKey, file) => gen(function* () {
|
|
1805
|
-
const path = yield* Path;
|
|
1806
2073
|
const boundary = plan.operations.find((candidate) => candidate.kind === "symlink_skill" && candidate.skillKey === skillKey);
|
|
1807
2074
|
if (boundary === undefined)
|
|
1808
2075
|
return yield* refuse("SKILL_SYMLINK_BOUNDARY_MISSING");
|
|
1809
|
-
|
|
2076
|
+
return yield* managedPath(boundary.sourceRoot, file);
|
|
2077
|
+
});
|
|
2078
|
+
var managedPath = (sourceRoot, file) => gen(function* () {
|
|
2079
|
+
const path = yield* Path;
|
|
2080
|
+
const root = path.resolve(sourceRoot);
|
|
1810
2081
|
const target = path.resolve(file);
|
|
1811
2082
|
if (!isPathWithin(path, root, target) || target === root)
|
|
1812
2083
|
return yield* refuse("SKILL_WRITE_OUTSIDE_ROOT");
|
|
@@ -1822,18 +2093,34 @@ var writeSkill = (plan, operation) => gen(function* () {
|
|
|
1822
2093
|
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
1823
2094
|
yield* fs.writeFileString(target, operation.content);
|
|
1824
2095
|
});
|
|
1825
|
-
var
|
|
2096
|
+
var checkoutGitTree = (url, ref) => gen(function* () {
|
|
1826
2097
|
const fs = yield* FileSystem;
|
|
1827
2098
|
const path = yield* Path;
|
|
1828
|
-
const
|
|
1829
|
-
const scratch = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-git-skill-" });
|
|
2099
|
+
const scratch = yield* fs.makeTempDirectoryScoped({ prefix: "kitman-git-" });
|
|
1830
2100
|
const repository = path.join(scratch, "repository");
|
|
1831
2101
|
const tree = path.join(scratch, "tree");
|
|
1832
2102
|
const git = (...args) => runCommand2("git", ["--git-dir", repository, ...args]);
|
|
1833
2103
|
yield* fs.makeDirectory(tree);
|
|
1834
2104
|
yield* runCommand2("git", ["init", "--quiet", "--bare", repository]);
|
|
1835
|
-
yield* git("fetch", "--quiet", "--depth", "1", "--",
|
|
2105
|
+
yield* git("fetch", "--quiet", "--depth", "1", "--", url, ref);
|
|
1836
2106
|
yield* git("--work-tree", tree, "checkout", "FETCH_HEAD", "--", ".");
|
|
2107
|
+
return tree;
|
|
2108
|
+
});
|
|
2109
|
+
var replaceWithCopy = (directory, target) => gen(function* () {
|
|
2110
|
+
const fs = yield* FileSystem;
|
|
2111
|
+
const path = yield* Path;
|
|
2112
|
+
const staged = `${target}.partial`;
|
|
2113
|
+
yield* fs.remove(staged, { recursive: true, force: true });
|
|
2114
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
2115
|
+
yield* fs.copy(directory, staged);
|
|
2116
|
+
yield* fs.remove(target, { recursive: true, force: true });
|
|
2117
|
+
yield* fs.rename(staged, target);
|
|
2118
|
+
});
|
|
2119
|
+
var fetchGitSkill = (plan, operation, checkout) => gen(function* () {
|
|
2120
|
+
const fs = yield* FileSystem;
|
|
2121
|
+
const path = yield* Path;
|
|
2122
|
+
const target = yield* managedSkillPath(plan, operation.skillKey, operation.path);
|
|
2123
|
+
const tree = yield* checkout(operation.url, operation.ref);
|
|
1837
2124
|
const skillDirectory = path.resolve(tree, operation.repositoryPath ?? ".");
|
|
1838
2125
|
if (!isPathWithin(path, tree, skillDirectory))
|
|
1839
2126
|
return yield* refuse("SKILL_SOURCE_OUTSIDE_ROOT");
|
|
@@ -1841,13 +2128,44 @@ var fetchGitSkill = (plan, operation) => gen(function* () {
|
|
|
1841
2128
|
return yield* refuse(`Git skill ${operation.skillName} links to a file outside itself in ${operation.url}@${operation.ref}; Kitman does not install linked files.`);
|
|
1842
2129
|
if (!(yield* fs.exists(path.join(skillDirectory, "SKILL.md"))))
|
|
1843
2130
|
return yield* refuse(`Git skill ${operation.skillName} has no SKILL.md at ${operation.repositoryPath ?? "the repository root"} in ${operation.url}@${operation.ref}.`);
|
|
1844
|
-
|
|
1845
|
-
|
|
2131
|
+
yield* replaceWithCopy(skillDirectory, target);
|
|
2132
|
+
});
|
|
2133
|
+
var writeCodexMarketplace = (operation) => gen(function* () {
|
|
2134
|
+
const fs = yield* FileSystem;
|
|
2135
|
+
const path = yield* Path;
|
|
2136
|
+
const target = yield* managedPath(operation.sourceRoot, operation.path);
|
|
2137
|
+
if (yield* isSymbolicLink(target))
|
|
2138
|
+
return yield* refuse("SKILL_WRITE_SYMLINK");
|
|
1846
2139
|
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
1847
|
-
yield* fs.
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2140
|
+
yield* fs.writeFileString(target, operation.content);
|
|
2141
|
+
});
|
|
2142
|
+
var fetchGitPlugin = (operation, checkout) => gen(function* () {
|
|
2143
|
+
const fs = yield* FileSystem;
|
|
2144
|
+
const path = yield* Path;
|
|
2145
|
+
const target = yield* managedPath(operation.sourceRoot, operation.path);
|
|
2146
|
+
const tree = yield* checkout(operation.url, operation.ref);
|
|
2147
|
+
if (yield* containsSymbolicLink(tree, tree))
|
|
2148
|
+
return yield* refuse(`Git plugin ${operation.pluginName} links to a file in ${operation.url}@${operation.ref}; Kitman does not install linked files.`);
|
|
2149
|
+
if (!(yield* fs.exists(path.join(tree, operation.manifest))))
|
|
2150
|
+
return yield* refuse(`Git plugin ${operation.pluginName} has no ${operation.manifest} at the root of ${operation.url}@${operation.ref}.`);
|
|
2151
|
+
yield* replaceWithCopy(tree, target);
|
|
2152
|
+
});
|
|
2153
|
+
var unlinkStaleLinks = (operation) => gen(function* () {
|
|
2154
|
+
const fs = yield* FileSystem;
|
|
2155
|
+
const path = yield* Path;
|
|
2156
|
+
if (!(yield* fs.exists(operation.path)))
|
|
2157
|
+
return;
|
|
2158
|
+
yield* assertNoSymlinkComponents(operation.path, "SKILL_TARGET_SYMLINK_PARENT");
|
|
2159
|
+
const sharedSkillPaths = new Set((yield* forEach(operation.sharedWith, (shared) => readManifestSkillPaths(shared.manifestPath))).flatMap((paths) => [...paths].map((skillPath) => path.resolve(skillPath))));
|
|
2160
|
+
for (const entry of yield* fs.readDirectory(operation.path)) {
|
|
2161
|
+
const link = path.join(operation.path, entry);
|
|
2162
|
+
if (operation.keepPaths.includes(link) || !(yield* isSymbolicLink(link)))
|
|
2163
|
+
continue;
|
|
2164
|
+
const linkTarget = path.resolve(operation.path, yield* fs.readLink(link));
|
|
2165
|
+
if (isPathWithin(path, operation.sourceRoot, linkTarget) && !sharedSkillPaths.has(linkTarget))
|
|
2166
|
+
yield* fs.remove(link);
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
1851
2169
|
var containsSymbolicLink = (root, directory) => gen(function* () {
|
|
1852
2170
|
const fs = yield* FileSystem;
|
|
1853
2171
|
const path = yield* Path;
|
|
@@ -1894,8 +2212,8 @@ var exposeSkill = (operation) => gen(function* () {
|
|
|
1894
2212
|
}
|
|
1895
2213
|
yield* fs.symlink(path.resolve(operation.sourcePath), targetPath);
|
|
1896
2214
|
});
|
|
1897
|
-
var isSymbolicLink = (file) =>
|
|
1898
|
-
var realPathOr = (file, reason) =>
|
|
2215
|
+
var isSymbolicLink = (file) => flatMap2(FileSystem, (fs) => fs.readLink(file).pipe(as(true), orElseSucceed(() => false)));
|
|
2216
|
+
var realPathOr = (file, reason) => flatMap2(FileSystem, (fs) => fs.realPath(file).pipe(mapError(() => new SkillMaterializationRefused({ message: reason }))));
|
|
1899
2217
|
var assertNoSymlinkComponents = (file, reason) => gen(function* () {
|
|
1900
2218
|
const path = yield* Path;
|
|
1901
2219
|
for (let current = path.resolve(file);; current = path.dirname(current)) {
|
|
@@ -1916,7 +2234,7 @@ function skillIdentity(skill) {
|
|
|
1916
2234
|
if (source.kind === "kitman")
|
|
1917
2235
|
return `kitman:${source.skillId ?? source.name ?? skill.name}:${skill.name}`;
|
|
1918
2236
|
if (source.kind === "git")
|
|
1919
|
-
return `git:${source.url}#${source.ref}:${source.path ?? ""}:${skill.name}`;
|
|
2237
|
+
return `git:${source.url}#${source.ref ?? ""}:${source.path ?? ""}:${skill.name}`;
|
|
1920
2238
|
if (source.kind === "package")
|
|
1921
2239
|
return `package:${source.packageName}@${source.version}:${source.path ?? ""}:${skill.name}`;
|
|
1922
2240
|
if (source.kind === "url")
|
|
@@ -1930,7 +2248,9 @@ var ProjectSkillDirectories = {
|
|
|
1930
2248
|
pi: ".pi/skills"
|
|
1931
2249
|
};
|
|
1932
2250
|
function skillBasePath2(path, harness, scope, rootDir, home) {
|
|
1933
|
-
|
|
2251
|
+
if (scope !== "user")
|
|
2252
|
+
return path.join(rootDir ?? ".", ProjectSkillDirectories[harness]);
|
|
2253
|
+
return harness === "claude-code" ? path.join(home, ".claude", "skills") : path.join(home, ".agents", "skills");
|
|
1934
2254
|
}
|
|
1935
2255
|
function skillMarkdown(name, contentMarkdown) {
|
|
1936
2256
|
const content = contentMarkdown.trimEnd();
|
|
@@ -1985,7 +2305,7 @@ var closeSocket = (conn, isOpen) => {
|
|
|
1985
2305
|
var makeNet = (options) => fromDuplex(contextWith((context) => {
|
|
1986
2306
|
let conn;
|
|
1987
2307
|
let isOpen = false;
|
|
1988
|
-
return
|
|
2308
|
+
return flatMap2(addFinalizer(get(context, Scope), sync(() => {
|
|
1989
2309
|
if (!conn)
|
|
1990
2310
|
return;
|
|
1991
2311
|
closeSocket(conn, isOpen);
|
|
@@ -2387,7 +2707,7 @@ class HerdrSocketClient {
|
|
|
2387
2707
|
});
|
|
2388
2708
|
}
|
|
2389
2709
|
sessionSnapshot() {
|
|
2390
|
-
return
|
|
2710
|
+
return flatMap2(this.request("session.snapshot", {}), (result) => {
|
|
2391
2711
|
const snapshot = isRecord(result) && result.type === "session_snapshot" ? result.snapshot : undefined;
|
|
2392
2712
|
if (!isRecord(snapshot) || !isString2(snapshot.version) || !isNumber(snapshot.protocol) || !Number.isInteger(snapshot.protocol) || !Array.isArray(snapshot.workspaces) || !Array.isArray(snapshot.tabs) || !Array.isArray(snapshot.panes) || !Array.isArray(snapshot.layouts) || !Array.isArray(snapshot.agents))
|
|
2393
2713
|
return fail4(fail2("herdr_session_snapshot_invalid"));
|
|
@@ -2502,7 +2822,7 @@ function isRecord(value) {
|
|
|
2502
2822
|
// packages/agent-runtime/src/herdr-server-supervisor.ts
|
|
2503
2823
|
class HerdrServerError extends TaggedError("HerdrServerError") {
|
|
2504
2824
|
}
|
|
2505
|
-
var
|
|
2825
|
+
var fail5 = (message) => new HerdrServerError({ message });
|
|
2506
2826
|
|
|
2507
2827
|
class HerdrServerSupervisor {
|
|
2508
2828
|
runnerId;
|
|
@@ -2534,9 +2854,9 @@ class HerdrServerSupervisor {
|
|
|
2534
2854
|
}
|
|
2535
2855
|
start = fnUntraced(function* () {
|
|
2536
2856
|
if (this.started && this.client !== undefined)
|
|
2537
|
-
return yield* this.client.sessionSnapshot().pipe(mapError((error) =>
|
|
2857
|
+
return yield* this.client.sessionSnapshot().pipe(mapError((error) => fail5(error.message)));
|
|
2538
2858
|
if (!["linux", "darwin"].includes(this.options.platform ?? process.platform))
|
|
2539
|
-
return yield*
|
|
2859
|
+
return yield* fail5("herdr_platform_unsupported");
|
|
2540
2860
|
const defaultSession = this.options.session === "default";
|
|
2541
2861
|
const runtime = defaultSession ? undefined : yield* this.prepareRuntime();
|
|
2542
2862
|
const socketClientOptions = this.options.socketClientOptions ?? {};
|
|
@@ -2577,7 +2897,7 @@ class HerdrServerSupervisor {
|
|
|
2577
2897
|
}
|
|
2578
2898
|
const snapshot = yield* awaitReadiness(socketClient, this.readinessTimeoutMs);
|
|
2579
2899
|
if (snapshot.protocol !== HERDR_SOCKET_PROTOCOL)
|
|
2580
|
-
return yield*
|
|
2900
|
+
return yield* fail5("herdr_protocol_mismatch");
|
|
2581
2901
|
return snapshot;
|
|
2582
2902
|
});
|
|
2583
2903
|
attachToDefaultSession = fnUntraced(function* (socketPath, socketClient) {
|
|
@@ -2585,7 +2905,7 @@ class HerdrServerSupervisor {
|
|
|
2585
2905
|
if (isFailure(running)) {
|
|
2586
2906
|
const message = running.failure.message;
|
|
2587
2907
|
if (message !== "herdr_socket_connect_failed" && message !== "herdr_socket_closed")
|
|
2588
|
-
return yield*
|
|
2908
|
+
return yield* fail5(message);
|
|
2589
2909
|
yield* (this.options.spawnServer ?? spawnServer)({
|
|
2590
2910
|
command: this.options.herdrCommand ?? "herdr",
|
|
2591
2911
|
args: ["server"],
|
|
@@ -2594,7 +2914,7 @@ class HerdrServerSupervisor {
|
|
|
2594
2914
|
}
|
|
2595
2915
|
const snapshot = isSuccess(running) ? running.success : yield* awaitReadiness(socketClient, this.readinessTimeoutMs);
|
|
2596
2916
|
if (snapshot.protocol !== HERDR_SOCKET_PROTOCOL)
|
|
2597
|
-
return yield*
|
|
2917
|
+
return yield* fail5("herdr_protocol_mismatch");
|
|
2598
2918
|
return snapshot;
|
|
2599
2919
|
});
|
|
2600
2920
|
prepareRuntime = fnUntraced(function* () {
|
|
@@ -2606,7 +2926,7 @@ class HerdrServerSupervisor {
|
|
|
2606
2926
|
fs.makeDirectory(path.dirname(directory), { recursive: true, mode: 448 }),
|
|
2607
2927
|
fs.makeDirectory(directory, { recursive: true, mode: 448 }),
|
|
2608
2928
|
fs.chmod(directory, 448)
|
|
2609
|
-
]), () =>
|
|
2929
|
+
]), () => fail5("herdr_runtime_unavailable"));
|
|
2610
2930
|
const sessionName = `kitman-herdr-${digest(this.runnerId)}`;
|
|
2611
2931
|
const runtime = {
|
|
2612
2932
|
directory,
|
|
@@ -2640,7 +2960,7 @@ var reclaimStaleRuntime = (runtime, runnerId, isAlive) => gen(function* () {
|
|
|
2640
2960
|
if (!socketExists && owner === undefined)
|
|
2641
2961
|
return false;
|
|
2642
2962
|
if (owner === undefined || owner.runnerId !== runnerId || owner.socketPath !== runtime.socketPath)
|
|
2643
|
-
return yield*
|
|
2963
|
+
return yield* fail5(`herdr_socket_ownership_unknown:${socketExists ? "socket_present" : "socket_missing"}:${owner === undefined ? "owner_missing" : "owner_mismatch"}`);
|
|
2644
2964
|
if (yield* isAlive(owner.pid))
|
|
2645
2965
|
return true;
|
|
2646
2966
|
yield* removeRuntimeFiles(runtime);
|
|
@@ -2651,8 +2971,8 @@ var awaitReadiness = (client, readinessTimeoutMs) => gen(function* () {
|
|
|
2651
2971
|
for (;; ) {
|
|
2652
2972
|
const remaining = deadline - (yield* currentTimeMillis);
|
|
2653
2973
|
if (remaining <= 0)
|
|
2654
|
-
return yield*
|
|
2655
|
-
const attempt = yield* result(client.sessionSnapshot().pipe(mapError((error) =>
|
|
2974
|
+
return yield* fail5("herdr_socket_connect_failed");
|
|
2975
|
+
const attempt = yield* result(client.sessionSnapshot().pipe(mapError((error) => fail5(error.message)), timeoutOrElse({ duration: remaining, orElse: () => fail4(fail5("herdr_readiness_timeout")) })));
|
|
2656
2976
|
if (isSuccess(attempt))
|
|
2657
2977
|
return attempt.success;
|
|
2658
2978
|
const message = attempt.failure.message;
|
|
@@ -2680,15 +3000,15 @@ var homeDirectory = map3(optionalEnv("HOME"), (home) => home ?? "/");
|
|
|
2680
3000
|
var temporaryDirectory = map3(optionalEnv("TMPDIR"), (tmp) => tmp ?? "/tmp");
|
|
2681
3001
|
var spawnServer = (input) => gen(function* () {
|
|
2682
3002
|
const spawner = yield* ChildProcessSpawner;
|
|
2683
|
-
const scope = yield*
|
|
2684
|
-
const handle = yield* spawner.spawn(
|
|
3003
|
+
const scope = yield* make7();
|
|
3004
|
+
const handle = yield* spawner.spawn(make13(input.command, [...input.args], {
|
|
2685
3005
|
env: { HERDR_SOCKET_PATH: input.socketPath },
|
|
2686
3006
|
extendEnv: true,
|
|
2687
3007
|
detached: true,
|
|
2688
3008
|
stdin: "ignore",
|
|
2689
3009
|
stdout: "ignore",
|
|
2690
3010
|
stderr: "ignore"
|
|
2691
|
-
})).pipe(provide(scope), mapError(() =>
|
|
3011
|
+
})).pipe(provide(scope), mapError(() => fail5("herdr_server_start_failed")));
|
|
2692
3012
|
yield* ignore(handle.unref);
|
|
2693
3013
|
return {
|
|
2694
3014
|
pid: handle.pid,
|
|
@@ -2708,7 +3028,7 @@ var writeOwnerMarker = (path, marker) => gen(function* () {
|
|
|
2708
3028
|
yield* fs.writeFileString(path, `${encodeJsonStringSync(marker)}
|
|
2709
3029
|
`, { mode: 384 });
|
|
2710
3030
|
yield* fs.chmod(path, 384);
|
|
2711
|
-
}).pipe(mapError(() =>
|
|
3031
|
+
}).pipe(mapError(() => fail5("herdr_runtime_unavailable")));
|
|
2712
3032
|
var readOwnerMarker = (path) => gen(function* () {
|
|
2713
3033
|
const fs = yield* FileSystem;
|
|
2714
3034
|
const text = yield* fs.readFileString(path);
|
|
@@ -2757,7 +3077,7 @@ function shortId(value) {
|
|
|
2757
3077
|
function isRecord2(value) {
|
|
2758
3078
|
return isObject(value) && value !== null && !Array.isArray(value);
|
|
2759
3079
|
}
|
|
2760
|
-
var requestRecord = (client, method, params, expectedType) =>
|
|
3080
|
+
var requestRecord = (client, method, params, expectedType) => flatMap2(client.request(method, params), (response) => {
|
|
2761
3081
|
const result = recordValue(response);
|
|
2762
3082
|
return result === undefined || result.type !== expectedType ? fail4(new HerdrHarnessError({ message: `${method}_response_invalid` })) : succeed5(result);
|
|
2763
3083
|
});
|
|
@@ -2907,7 +3227,7 @@ var HarnessAdapters = [
|
|
|
2907
3227
|
auth: ["~/.codex"],
|
|
2908
3228
|
providerHosts: ["api.openai.com", "auth.openai.com", "chatgpt.com"],
|
|
2909
3229
|
effortArgs: ["-c", 'model_reasoning_effort="{effort}"'],
|
|
2910
|
-
autoPermissionArgs: ["--dangerously-bypass-approvals-and-sandbox"],
|
|
3230
|
+
autoPermissionArgs: ["--dangerously-bypass-approvals-and-sandbox", "--dangerously-bypass-hook-trust"],
|
|
2911
3231
|
firstMessage: "herdr_prompt",
|
|
2912
3232
|
mcp: { kind: "toml_overrides", flag: "-c" },
|
|
2913
3233
|
skills: { kind: "skills_config", root: "skills" },
|
|
@@ -3476,7 +3796,7 @@ var executeHerdrStepAttempt = (input) => gen(function* () {
|
|
|
3476
3796
|
});
|
|
3477
3797
|
yield* input.harness.prompt({ targetId: launched.targetId, text: firstMessage(input.agent, worktreePath) });
|
|
3478
3798
|
const steering = input.control.steering;
|
|
3479
|
-
const deliveries = steering === undefined ? undefined : yield* forkScoped(forever(
|
|
3799
|
+
const deliveries = steering === undefined ? undefined : yield* forkScoped(forever(flatMap2(steering.next, (note) => input.harness.prompt({ targetId: launched.targetId, text: note.text }).pipe(matchEffect({
|
|
3480
3800
|
onSuccess: () => steering.report(note, true),
|
|
3481
3801
|
onFailure: () => steering.report(note, false)
|
|
3482
3802
|
})))));
|
|
@@ -3887,7 +4207,7 @@ var findSession = (adapter, place) => gen(function* () {
|
|
|
3887
4207
|
}).pipe(orElseSucceed(() => {
|
|
3888
4208
|
return;
|
|
3889
4209
|
}));
|
|
3890
|
-
var packSession = (path) => map3(
|
|
4210
|
+
var packSession = (path) => map3(flatMap2(FileSystem, (fs) => fs.readFile(path)), (content) => new Uint8Array(gzipSync(content)));
|
|
3891
4211
|
var restoreSession = (adapter, place) => gen(function* () {
|
|
3892
4212
|
const fs = yield* FileSystem;
|
|
3893
4213
|
const location = adapter.session?.location;
|
|
@@ -4158,8 +4478,8 @@ function runReplica(control, executor, signals) {
|
|
|
4158
4478
|
if (work === undefined)
|
|
4159
4479
|
return { status: "idle" };
|
|
4160
4480
|
const lease = leaseInput(work);
|
|
4161
|
-
const workHeartbeat = sleep(heartbeatInterval(work)).pipe(
|
|
4162
|
-
const pushedStop = signals === undefined ? never : signals.stopped(work.id).pipe(
|
|
4481
|
+
const workHeartbeat = sleep(heartbeatInterval(work)).pipe(flatMap2(() => call("heartbeatWork", () => control.heartbeatWork(work.id, lease))), repeat({ until: (heartbeat) => heartbeat?.settled === true }), as({ status: "settled" }));
|
|
4482
|
+
const pushedStop = signals === undefined ? never : signals.stopped(work.id).pipe(flatMap2((stop) => stop === "canceled" ? fail4(new RunnerReplicaCancellation) : succeed5({ status: "settled" })), ensuring(signals.forget(work.id)));
|
|
4163
4483
|
let completed = false;
|
|
4164
4484
|
let leaseLost = false;
|
|
4165
4485
|
return yield* gen(function* () {
|
|
@@ -4543,7 +4863,101 @@ function runnerStepAttemptFromAgentStep(work) {
|
|
|
4543
4863
|
}
|
|
4544
4864
|
};
|
|
4545
4865
|
}
|
|
4866
|
+
// packages/agent-runtime/src/codex-hook-trust.ts
|
|
4867
|
+
class CodexAppServerFailed extends TaggedError("CodexAppServerFailed") {
|
|
4868
|
+
get message() {
|
|
4869
|
+
return this.reason;
|
|
4870
|
+
}
|
|
4871
|
+
}
|
|
4872
|
+
var RpcResponse = Struct({
|
|
4873
|
+
id: Finite,
|
|
4874
|
+
result: optional(Unknown),
|
|
4875
|
+
error: optional(Struct({ message: String2 }))
|
|
4876
|
+
});
|
|
4877
|
+
var HooksList = Struct({
|
|
4878
|
+
data: ArraySchema(Struct({
|
|
4879
|
+
hooks: ArraySchema(Struct({
|
|
4880
|
+
key: String2,
|
|
4881
|
+
pluginId: optional(NullOr(String2)),
|
|
4882
|
+
currentHash: String2,
|
|
4883
|
+
trustStatus: String2
|
|
4884
|
+
}))
|
|
4885
|
+
}))
|
|
4886
|
+
});
|
|
4887
|
+
var codexAppServer = (binary = "codex") => (env) => gen(function* () {
|
|
4888
|
+
const spawner = yield* ChildProcessSpawner;
|
|
4889
|
+
const input = yield* unbounded();
|
|
4890
|
+
const pending = new Map;
|
|
4891
|
+
let lastId = 0;
|
|
4892
|
+
const handle = yield* spawner.spawn(make13(binary, ["app-server"], {
|
|
4893
|
+
cwd: env.home,
|
|
4894
|
+
extendEnv: true,
|
|
4895
|
+
env: { HOME: env.home, CODEX_HOME: env.codexHome },
|
|
4896
|
+
stdin: fromQueue(input),
|
|
4897
|
+
stderr: "ignore"
|
|
4898
|
+
})).pipe(catchReason("PlatformError", "NotFound", () => fail4(new CodexAppServerFailed({ reason: `${binary} is not on the PATH` }))), mapError((error) => new CodexAppServerFailed({ reason: error.message })));
|
|
4899
|
+
const settle = (line) => match(flatMap(decodeJsonStringOption(line), decodeUnknownOption(RpcResponse)), {
|
|
4900
|
+
onNone: () => void_2,
|
|
4901
|
+
onSome: (response) => {
|
|
4902
|
+
const deferred = pending.get(response.id);
|
|
4903
|
+
if (deferred === undefined)
|
|
4904
|
+
return void_2;
|
|
4905
|
+
pending.delete(response.id);
|
|
4906
|
+
return response.error === undefined ? succeed3(deferred, response.result) : fail3(deferred, new CodexAppServerFailed({ reason: response.error.message }));
|
|
4907
|
+
}
|
|
4908
|
+
});
|
|
4909
|
+
yield* handle.stdout.pipe(decodeText, splitLines, runForEach(settle), ignore, ensuring(forEach(pending.values(), (deferred) => fail3(deferred, new CodexAppServerFailed({ reason: `${binary} app-server exited` })))), forkScoped);
|
|
4910
|
+
const send = (message) => offer(input, new TextEncoder().encode(`${encodeJsonStringSync(message)}
|
|
4911
|
+
`));
|
|
4912
|
+
return {
|
|
4913
|
+
request: (method, params) => gen(function* () {
|
|
4914
|
+
const id = ++lastId;
|
|
4915
|
+
const deferred = yield* make6();
|
|
4916
|
+
pending.set(id, deferred);
|
|
4917
|
+
yield* send({ id, method, params });
|
|
4918
|
+
return yield* _await(deferred).pipe(mapError((error) => new CodexAppServerFailed({ reason: `${method}: ${error.message}` })));
|
|
4919
|
+
}),
|
|
4920
|
+
notify: (method) => asVoid(send({ method }))
|
|
4921
|
+
};
|
|
4922
|
+
});
|
|
4923
|
+
var CodexAppServer = Reference("kitman/agent-runtime/CodexAppServer", {
|
|
4924
|
+
defaultValue: () => codexAppServer()
|
|
4925
|
+
});
|
|
4926
|
+
var trustCodexPluginHooks = (input) => gen(function* () {
|
|
4927
|
+
const launch = yield* CodexAppServer;
|
|
4928
|
+
const codex = yield* launch({ home: input.home, codexHome: `${input.home}/.codex` });
|
|
4929
|
+
const pluginIds = new Set(input.plugins.map((plugin) => `${plugin}@kitman`));
|
|
4930
|
+
yield* codex.request("initialize", { clientInfo: { name: "kitman", title: "Kitman", version: "1" } });
|
|
4931
|
+
yield* codex.notify("initialized");
|
|
4932
|
+
for (const pluginName of input.plugins)
|
|
4933
|
+
yield* codex.request("plugin/install", { marketplacePath: input.marketplacePath, pluginName });
|
|
4934
|
+
const listed = yield* decodeUnknownEffect(HooksList)(yield* codex.request("hooks/list", { cwds: [input.home] })).pipe(mapError(() => new CodexAppServerFailed({ reason: "hooks/list: unexpected response" })));
|
|
4935
|
+
const untrusted = listed.data.flatMap((entry) => entry.hooks).filter((hook) => pluginIds.has(hook.pluginId ?? "") && (hook.trustStatus === "untrusted" || hook.trustStatus === "modified"));
|
|
4936
|
+
if (untrusted.length > 0)
|
|
4937
|
+
yield* codex.request("config/batchWrite", {
|
|
4938
|
+
edits: [
|
|
4939
|
+
{
|
|
4940
|
+
keyPath: "hooks.state",
|
|
4941
|
+
mergeStrategy: "upsert",
|
|
4942
|
+
value: Object.fromEntries(untrusted.map((hook) => [hook.key, { trusted_hash: hook.currentHash }]))
|
|
4943
|
+
}
|
|
4944
|
+
],
|
|
4945
|
+
reloadUserConfig: true
|
|
4946
|
+
});
|
|
4947
|
+
return [];
|
|
4948
|
+
}).pipe(scoped, timeoutOrElse({
|
|
4949
|
+
duration: "1 minute",
|
|
4950
|
+
orElse: () => fail4(new CodexAppServerFailed({ reason: "timed out" }))
|
|
4951
|
+
}), catch_((error) => succeed5(input.plugins.map((plugin) => `Kitman could not record trust for ${plugin}'s hooks (${error.message}); Codex will ask in /hooks.`))));
|
|
4952
|
+
|
|
4546
4953
|
// packages/agent-runtime/src/materialization-plan.ts
|
|
4954
|
+
var HarnessUpdaters = {
|
|
4955
|
+
"claude-code": ["claude", "update"],
|
|
4956
|
+
codex: ["codex", "update"],
|
|
4957
|
+
opencode: ["opencode", "upgrade"],
|
|
4958
|
+
pi: ["pi", "update", "--self"]
|
|
4959
|
+
};
|
|
4960
|
+
|
|
4547
4961
|
class AgentEnvironmentPlanApplyError extends TaggedError("AgentEnvironmentPlanApplyError") {
|
|
4548
4962
|
get message() {
|
|
4549
4963
|
return `AgentEnvironment plan apply failed during ${this.phase}: ${this.cause.message}`;
|
|
@@ -4556,11 +4970,17 @@ var previewAgentEnvironmentPlan = fnUntraced(function* (input) {
|
|
|
4556
4970
|
...input.scope === undefined ? undefined : { scope: input.scope },
|
|
4557
4971
|
...input.cacheRoot === undefined ? undefined : { cacheRoot: input.cacheRoot },
|
|
4558
4972
|
...input.noInstall === undefined ? undefined : { noInstall: input.noInstall },
|
|
4559
|
-
...input.approvedSkillSources === undefined ? undefined : { approvedSkillSources: input.approvedSkillSources }
|
|
4973
|
+
...input.approvedSkillSources === undefined ? undefined : { approvedSkillSources: input.approvedSkillSources },
|
|
4974
|
+
...input.requireReviewedSources === undefined ? undefined : { requireReviewedSources: input.requireReviewedSources },
|
|
4975
|
+
...input.versions === undefined ? undefined : { versions: input.versions }
|
|
4560
4976
|
});
|
|
4561
4977
|
const harnessConfig = yield* previewAgentEnvironmentConfigSync({
|
|
4562
4978
|
environment: input.environment,
|
|
4563
4979
|
materializedSkills: skills.materializedSkills,
|
|
4980
|
+
materializedPlugins: skills.materializedPlugins,
|
|
4981
|
+
skippedPlugins: skills.skippedPlugins,
|
|
4982
|
+
...input.noInstall === undefined ? undefined : { noInstall: input.noInstall },
|
|
4983
|
+
...input.versions === undefined ? undefined : { versions: input.versions },
|
|
4564
4984
|
...input.syncKit === undefined ? undefined : { syncKit: input.syncKit },
|
|
4565
4985
|
...input.rootDir === undefined ? undefined : { rootDir: input.rootDir },
|
|
4566
4986
|
...input.scope === undefined ? undefined : { scope: input.scope },
|
|
@@ -4571,19 +4991,50 @@ var previewAgentEnvironmentPlan = fnUntraced(function* (input) {
|
|
|
4571
4991
|
...input.syncKit === undefined ? undefined : { syncKitHash: input.syncKit.syncKitHash },
|
|
4572
4992
|
skills,
|
|
4573
4993
|
harnessConfig,
|
|
4994
|
+
...input.versions === "latest" && input.noInstall !== true ? { harnessUpdater: HarnessUpdaters[input.environment.harness] } : undefined,
|
|
4574
4995
|
operationCount: skills.operations.length + harnessConfig.operations.length,
|
|
4575
4996
|
warnings: [...skills.warnings, ...harnessConfig.warnings].map((item) => item.message)
|
|
4576
4997
|
};
|
|
4577
4998
|
});
|
|
4578
4999
|
function composeAgentEnvironmentSyncPlan(targets) {
|
|
4579
|
-
|
|
5000
|
+
const unlinks = targets.flatMap((target) => target.skills.operations.flatMap((operation) => operation.kind === "unlink_stale_links" ? [operation] : []));
|
|
5001
|
+
const composed = targets.map((target) => ({
|
|
5002
|
+
...target,
|
|
5003
|
+
skills: {
|
|
5004
|
+
...target.skills,
|
|
5005
|
+
operations: target.skills.operations.map((operation) => operation.kind === "unlink_stale_links" ? {
|
|
5006
|
+
...operation,
|
|
5007
|
+
keepPaths: unlinks.flatMap((unlink) => unlink.path === operation.path ? unlink.keepPaths : []),
|
|
5008
|
+
sharedWith: operation.sharedWith.filter((shared) => !targets.some((other) => other.harness === shared.harness))
|
|
5009
|
+
} : operation)
|
|
5010
|
+
}
|
|
5011
|
+
}));
|
|
5012
|
+
return { version: 1, planId: canonicalSha256(composed), targets: composed };
|
|
4580
5013
|
}
|
|
5014
|
+
var updateHarness = (argv) => gen(function* () {
|
|
5015
|
+
const run = yield* CommandRunner2;
|
|
5016
|
+
const failure = yield* run(argv).pipe(map3((result) => result.exitCode === 0 ? undefined : result.output.trim().split(`
|
|
5017
|
+
`).at(-1) ?? `exit code ${result.exitCode}`), catch_((error) => succeed5(error.message)));
|
|
5018
|
+
return failure === undefined ? [] : [`Kitman could not update ${argv[0]} with \`${argv.join(" ")}\`: ${failure}`];
|
|
5019
|
+
});
|
|
4581
5020
|
var applyAgentEnvironmentPlans = fnUntraced(function* (plan) {
|
|
4582
|
-
|
|
4583
|
-
|
|
5021
|
+
yield* gen(function* () {
|
|
5022
|
+
const checkout = yield* gitCheckouts;
|
|
5023
|
+
for (const target of plan.targets)
|
|
5024
|
+
yield* applyAgentEnvironmentMaterialization(target.skills, checkout).pipe(mapError((cause) => new AgentEnvironmentPlanApplyError({ phase: "skills", cause })));
|
|
5025
|
+
}).pipe(scoped);
|
|
5026
|
+
const updateWarnings = yield* forEach(plan.targets, (target) => target.harnessUpdater === undefined ? succeed5([]) : updateHarness(target.harnessUpdater));
|
|
4584
5027
|
for (const target of plan.targets)
|
|
4585
5028
|
yield* applyAgentEnvironmentConfigSync(target.harnessConfig).pipe(mapError((cause) => new AgentEnvironmentPlanApplyError({ phase: "harness_config", cause })));
|
|
4586
|
-
|
|
5029
|
+
const targets = yield* forEach(plan.targets, (target, index) => map3(target.skills.codexHookTrust === undefined ? succeed5([]) : trustCodexPluginHooks({
|
|
5030
|
+
...target.skills.codexHookTrust,
|
|
5031
|
+
plugins: target.skills.materializedPlugins.map((plugin) => plugin.name)
|
|
5032
|
+
}), (trustWarnings) => [...updateWarnings[index] ?? [], ...trustWarnings]).pipe(map3((warnings) => ({
|
|
5033
|
+
...target,
|
|
5034
|
+
skills: { ...target.skills, warnings: [...target.skills.warnings, ...warnings.map((message) => ({ message }))] },
|
|
5035
|
+
warnings: [...target.warnings, ...warnings]
|
|
5036
|
+
}))));
|
|
5037
|
+
return { ...plan, targets };
|
|
4587
5038
|
});
|
|
4588
5039
|
// packages/agent-runtime/src/herdr-executor-surface.ts
|
|
4589
5040
|
class HerdrExecutorSurfaceError extends TaggedError("HerdrExecutorSurfaceError") {
|
|
@@ -4619,7 +5070,7 @@ function herdrExecutorSurface(input) {
|
|
|
4619
5070
|
executionClient: {
|
|
4620
5071
|
request: (method, params) => supervisor.executionClient.request(method, params).pipe(tap((response) => record(method, response)))
|
|
4621
5072
|
},
|
|
4622
|
-
start: () => supervisor.start().pipe(mapError((error) => new HerdrExecutorSurfaceError({ reason: "start_failed", message: error.message })),
|
|
5073
|
+
start: () => supervisor.start().pipe(mapError((error) => new HerdrExecutorSurfaceError({ reason: "start_failed", message: error.message })), flatMap2((readiness) => readiness.protocol === HERDR_SOCKET_PROTOCOL ? void_2 : fail4(new HerdrExecutorSurfaceError({ reason: "start_failed", message: "herdr_protocol_mismatch" })))),
|
|
4623
5074
|
stop: () => andThen(prune, supervisor.stop())
|
|
4624
5075
|
};
|
|
4625
5076
|
}
|
|
@@ -4639,10 +5090,10 @@ var handshake = [
|
|
|
4639
5090
|
`).join("");
|
|
4640
5091
|
var kitmanMcpPreflight = (mcp) => gen(function* () {
|
|
4641
5092
|
const spawner = yield* ChildProcessSpawner;
|
|
4642
|
-
const server = yield* spawner.spawn(
|
|
5093
|
+
const server = yield* spawner.spawn(make13(mcp.command, [...mcp.args], {
|
|
4643
5094
|
extendEnv: true,
|
|
4644
5095
|
env: mcp.env,
|
|
4645
|
-
stdin: { stream:
|
|
5096
|
+
stdin: { stream: make11(new TextEncoder().encode(handshake)), endOnDone: false },
|
|
4646
5097
|
stderr: "ignore"
|
|
4647
5098
|
}));
|
|
4648
5099
|
const reply = yield* server.stdout.pipe(decodeText, splitLines, map4((line) => recordValue(getOrUndefined(decodeJsonStringOption(line)))), filter((message) => message?.id === 2), runHead);
|
|
@@ -4714,8 +5165,8 @@ var programProcessExecutor = (input) => (work) => gen(function* () {
|
|
|
4714
5165
|
};
|
|
4715
5166
|
const exitCode = sandbox === undefined ? yield* gen(function* () {
|
|
4716
5167
|
const spawner = yield* ChildProcessSpawner;
|
|
4717
|
-
const handle = yield* spawner.spawn(
|
|
4718
|
-
stdin:
|
|
5168
|
+
const handle = yield* spawner.spawn(make13(input.bun ?? process.execPath, [file], {
|
|
5169
|
+
stdin: make11(new TextEncoder().encode(stdin)),
|
|
4719
5170
|
stdout: "inherit",
|
|
4720
5171
|
stderr: "inherit",
|
|
4721
5172
|
extendEnv: false,
|
|
@@ -4867,6 +5318,7 @@ export {
|
|
|
4867
5318
|
AgentRuntimePackageName,
|
|
4868
5319
|
BundleRefused,
|
|
4869
5320
|
CommandFailed2 as CommandFailed,
|
|
5321
|
+
CommandRunner2 as CommandRunner,
|
|
4870
5322
|
ContainerError,
|
|
4871
5323
|
EgressProxyImage,
|
|
4872
5324
|
HERDR_SOCKET_PROTOCOL,
|
|
@@ -4903,6 +5355,7 @@ export {
|
|
|
4903
5355
|
dockerContainerRunner,
|
|
4904
5356
|
dockerMissing,
|
|
4905
5357
|
executeHerdrStepAttempt,
|
|
5358
|
+
gitCheckouts,
|
|
4906
5359
|
harnessAdapter,
|
|
4907
5360
|
herdrAgentBackendLayer,
|
|
4908
5361
|
herdrExecutionExtension2 as herdrExecutionExtension,
|
|
@@ -4933,6 +5386,7 @@ export {
|
|
|
4933
5386
|
previewAgentEnvironmentPlan,
|
|
4934
5387
|
probeSandboxHost,
|
|
4935
5388
|
programProcessExecutor,
|
|
5389
|
+
readManifestSkillPaths,
|
|
4936
5390
|
recordList,
|
|
4937
5391
|
recordValue,
|
|
4938
5392
|
refuseAgentRequest,
|
|
@@ -4968,6 +5422,7 @@ export {
|
|
|
4968
5422
|
stringValue,
|
|
4969
5423
|
superviseReplica,
|
|
4970
5424
|
verificationExecutor,
|
|
5425
|
+
withSourceResolutionCache,
|
|
4971
5426
|
writeAgentEnvironmentConfigFile,
|
|
4972
5427
|
writeSrtSettings
|
|
4973
5428
|
};
|