@ninemind/agentgem 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/dist/gem/acpRecommender.js +259 -0
- package/dist/gem/acpRun.js +156 -0
- package/dist/gem/acpSession.js +79 -0
- package/dist/gem/analysisCache.js +55 -0
- package/dist/gem/archive.js +17 -0
- package/dist/gem/binPath.js +9 -0
- package/dist/gem/buildGem.js +4 -1
- package/dist/gem/channels.js +29 -0
- package/dist/gem/credentials.js +3 -2
- package/dist/gem/distill.js +162 -0
- package/dist/gem/draftStage.js +77 -0
- package/dist/gem/gemVerify.js +35 -0
- package/dist/gem/inputError.js +21 -0
- package/dist/gem/registry.js +23 -4
- package/dist/gem/runGem.js +161 -0
- package/dist/gem/safeFetch.js +112 -0
- package/dist/gem/sandbox.js +37 -0
- package/dist/gem/sandboxLaunch.js +55 -0
- package/dist/gem/scrub.js +108 -0
- package/dist/gem/search.js +34 -0
- package/dist/gem/share.js +21 -0
- package/dist/gem/targets.js +280 -16
- package/dist/gem/testbedFlavors.js +1 -0
- package/dist/gem/workflowScan.js +0 -0
- package/dist/gem/workspaces.js +4 -3
- package/dist/gem.controller.js +151 -16
- package/dist/gem.tools.js +53 -5
- package/dist/gemRunStream.js +67 -0
- package/dist/index.js +15 -0
- package/dist/originGuard.js +36 -0
- package/dist/public/index.html +444 -10
- package/dist/schemas.js +180 -7
- package/dist/workflowStream.js +78 -0
- package/package.json +7 -2
package/dist/schemas.js
CHANGED
|
@@ -34,11 +34,24 @@ export const HookArtifactSchema = z.object({
|
|
|
34
34
|
source: z.string().optional(),
|
|
35
35
|
secretRefs: z.array(z.object({ name: z.string(), location: z.string() })).optional(),
|
|
36
36
|
});
|
|
37
|
+
export const ChannelPlatformSchema = z.enum(["slack", "telegram", "discord", "teams", "twilio", "github"]);
|
|
38
|
+
export const ChannelArtifactSchema = z.object({
|
|
39
|
+
type: z.literal("channel"),
|
|
40
|
+
name: z.string(),
|
|
41
|
+
platform: ChannelPlatformSchema,
|
|
42
|
+
secretRefs: z.array(z.object({ name: z.string(), location: z.string() })),
|
|
43
|
+
description: z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
// Declared channels on a request body. Shared by every endpoint that builds a Gem from a selection
|
|
46
|
+
// (gem preview, materialize, archive, create-workspace, publish) so channels are never dropped on one
|
|
47
|
+
// path while present on another. Each platform entry becomes a channel artifact via buildGem.
|
|
48
|
+
export const ChannelDeclSchema = z.array(z.object({ platform: ChannelPlatformSchema, name: z.string().optional() })).optional();
|
|
37
49
|
export const GemArtifactSchema = z.discriminatedUnion("type", [
|
|
38
50
|
SkillArtifactSchema,
|
|
39
51
|
McpServerArtifactSchema,
|
|
40
52
|
InstructionsArtifactSchema,
|
|
41
53
|
HookArtifactSchema,
|
|
54
|
+
ChannelArtifactSchema,
|
|
42
55
|
]);
|
|
43
56
|
export const SecretRequirementSchema = z.object({
|
|
44
57
|
name: z.string(),
|
|
@@ -105,25 +118,48 @@ export const GemSelectionSchema = z.union([
|
|
|
105
118
|
projects: ProjectSelectionSchema.optional(),
|
|
106
119
|
}),
|
|
107
120
|
]);
|
|
121
|
+
// A draft skill distilled from the builtin procedure (proposal §2). status is
|
|
122
|
+
// always "draft" — never installed by this pipeline. Defined before the build
|
|
123
|
+
// request schemas that reference it (module load order).
|
|
124
|
+
export const DistilledSkillSchema = z.object({
|
|
125
|
+
name: z.string(),
|
|
126
|
+
description: z.string(),
|
|
127
|
+
triggers: z.array(z.string()),
|
|
128
|
+
tools: z.array(z.string()),
|
|
129
|
+
mutating: z.boolean(),
|
|
130
|
+
body: z.string(),
|
|
131
|
+
evidence: z.object({
|
|
132
|
+
sessions: z.number(),
|
|
133
|
+
exampleSequence: z.array(z.string()),
|
|
134
|
+
root: z.string(),
|
|
135
|
+
}),
|
|
136
|
+
status: z.literal("draft"),
|
|
137
|
+
confidence: z.enum(["high", "medium", "low"]),
|
|
138
|
+
});
|
|
108
139
|
export const GemRequestSchema = z.object({
|
|
109
140
|
selection: GemSelectionSchema,
|
|
110
141
|
name: z.string().optional(),
|
|
111
142
|
dir: z.string().optional(),
|
|
112
143
|
projects: z.array(z.string()).optional(),
|
|
113
144
|
checks: z.array(GemCheckSchema).optional(),
|
|
145
|
+
channels: ChannelDeclSchema,
|
|
146
|
+
// Accepted distilled drafts to fold into the build — staged into the inventory
|
|
147
|
+
// (by evidence.root) before resolution, so a selection can reference one by name.
|
|
148
|
+
distilledDrafts: z.array(DistilledSkillSchema).optional(),
|
|
114
149
|
});
|
|
115
150
|
export const ScaffoldChecksRequestSchema = z.object({
|
|
116
151
|
selection: GemSelectionSchema,
|
|
117
152
|
name: z.string().optional(),
|
|
118
153
|
dir: z.string().optional(),
|
|
119
154
|
projects: z.array(z.string()).optional(),
|
|
155
|
+
distilledDrafts: z.array(DistilledSkillSchema).optional(),
|
|
120
156
|
});
|
|
121
157
|
export const ScaffoldChecksResponseSchema = z.object({ checks: z.array(GemCheckSchema) });
|
|
122
158
|
const TARGET_IDS = Object.keys(TARGET_REGISTRY);
|
|
123
159
|
export const TargetIdSchema = z.enum(TARGET_IDS);
|
|
124
160
|
export const SkippedArtifactSchema = z.object({
|
|
125
161
|
artifact: z.string(),
|
|
126
|
-
type: z.enum(["skill", "mcp_server", "instructions", "hook"]),
|
|
162
|
+
type: z.enum(["skill", "mcp_server", "instructions", "hook", "channel"]),
|
|
127
163
|
reason: z.string(),
|
|
128
164
|
});
|
|
129
165
|
export const MaterializeResponseSchema = z.object({
|
|
@@ -140,7 +176,7 @@ export const GemLockSchema = z.object({
|
|
|
140
176
|
signature: z.string().nullable(),
|
|
141
177
|
});
|
|
142
178
|
export const GemManifestArtifactSchema = z.object({
|
|
143
|
-
type: z.enum(["skill", "mcp_server", "instructions", "hook"]),
|
|
179
|
+
type: z.enum(["skill", "mcp_server", "instructions", "hook", "channel"]),
|
|
144
180
|
name: z.string(),
|
|
145
181
|
path: z.string(),
|
|
146
182
|
description: z.string().optional(),
|
|
@@ -162,24 +198,31 @@ export const ArchiveRequestSchema = z.object({
|
|
|
162
198
|
dir: z.string().optional(),
|
|
163
199
|
projects: z.array(z.string()).optional(),
|
|
164
200
|
outDir: z.string().optional(), // when set, write the tree here and return its path
|
|
201
|
+
outFile: z.string().optional(), // when set, write one portable .gem (tar.gz) here
|
|
165
202
|
tar: z.boolean().optional(), // when true, also return the tree as a base64 .tar.gz
|
|
203
|
+
channels: ChannelDeclSchema,
|
|
166
204
|
});
|
|
167
205
|
export const ArchiveResponseSchema = z.object({
|
|
168
206
|
files: z.record(z.string(), z.string()),
|
|
169
207
|
lock: GemLockSchema,
|
|
170
208
|
skipped: z.array(SkippedArtifactSchema),
|
|
171
209
|
path: z.string().nullable(),
|
|
210
|
+
gemFile: z.string().nullable(), // path to the written .gem when `outFile` was set, else null
|
|
172
211
|
tarGz: z.string().nullable(), // base64 .tar.gz when `tar` was requested, else null
|
|
173
212
|
});
|
|
174
213
|
export const MaterializeRequestSchema = z.object({
|
|
175
214
|
selection: GemSelectionSchema.optional(),
|
|
176
215
|
archivePath: z.string().optional(),
|
|
216
|
+
gemPath: z.string().optional(), // install from a single .gem (tar.gz) file on disk
|
|
217
|
+
gemUrl: z.string().optional(), // install from a .gem fetched over http(s)
|
|
177
218
|
target: TargetIdSchema,
|
|
178
219
|
name: z.string().optional(),
|
|
179
220
|
dir: z.string().optional(),
|
|
180
221
|
projects: z.array(z.string()).optional(),
|
|
181
|
-
|
|
182
|
-
|
|
222
|
+
a2aServer: z.boolean().optional(), // a2a target: also emit the runnable server, not just the Agent Card
|
|
223
|
+
channels: ChannelDeclSchema, // applied only when building from `selection` (ignored for archive/gem sources)
|
|
224
|
+
}).refine((d) => d.selection !== undefined || d.archivePath !== undefined || d.gemPath !== undefined || d.gemUrl !== undefined, {
|
|
225
|
+
message: "provide one of selection, archivePath, gemPath, or gemUrl",
|
|
183
226
|
});
|
|
184
227
|
export const DeployTargetIdSchema = z.enum(deployTargetIds);
|
|
185
228
|
export const DeployReadyQuerySchema = z.object({ target: DeployTargetIdSchema.optional() });
|
|
@@ -193,6 +236,7 @@ export const PublishPreviewRequestSchema = z.object({
|
|
|
193
236
|
dir: z.string().optional(),
|
|
194
237
|
projects: z.array(z.string()).optional(),
|
|
195
238
|
target: DeployTargetIdSchema.optional(),
|
|
239
|
+
channels: ChannelDeclSchema,
|
|
196
240
|
});
|
|
197
241
|
export const PublishRequestSchema = PublishPreviewRequestSchema.extend({ requestId: z.string().min(8).max(128), wsName: z.string().optional() });
|
|
198
242
|
const ManagedAgentPayloadSchema = z.object({
|
|
@@ -236,6 +280,41 @@ export const PublishResultSchema = z.discriminatedUnion("kind", [ManagedAgentRes
|
|
|
236
280
|
export const DirQuerySchema = z.object({ dir: z.string().optional(), projects: z.string().optional() });
|
|
237
281
|
export const PickQuerySchema = z.object({});
|
|
238
282
|
export const PickFolderSchema = z.object({ path: z.string().nullable() });
|
|
283
|
+
// ── Workflow-aware Gem recommendation ──
|
|
284
|
+
export const WorkflowAnalyzeRequestSchema = z.object({
|
|
285
|
+
dir: z.string().optional(), // .claude dir (resolveDirs handles the default)
|
|
286
|
+
root: z.string(), // the project root to analyze (one of the discovered cwds)
|
|
287
|
+
});
|
|
288
|
+
const RecommendedItemSchema = z.object({
|
|
289
|
+
// mirrors ArtifactType (incl. "channel") — RecommendedItem.type is ArtifactType, so this must stay in sync
|
|
290
|
+
type: z.enum(["skill", "mcp_server", "instructions", "hook", "channel"]),
|
|
291
|
+
name: z.string(),
|
|
292
|
+
reason: z.string(),
|
|
293
|
+
root: z.string().nullable(), // project root, or null for a global/plugin artifact
|
|
294
|
+
});
|
|
295
|
+
// One candidate Gem, carrying its own ready-to-POST GemSelection.
|
|
296
|
+
const GemCandidateSchema = z.object({
|
|
297
|
+
name: z.string(),
|
|
298
|
+
description: z.string(),
|
|
299
|
+
root: z.string(),
|
|
300
|
+
includeInstructions: z.boolean(),
|
|
301
|
+
include: z.array(RecommendedItemSchema),
|
|
302
|
+
confidence: z.enum(["high", "medium", "low"]),
|
|
303
|
+
selection: z.record(z.string(), z.unknown()), // a GemSelection; buildGem validates structurally at /api/gem
|
|
304
|
+
});
|
|
305
|
+
export const WorkflowAnalyzeResponseSchema = z.object({
|
|
306
|
+
candidates: z.array(GemCandidateSchema),
|
|
307
|
+
gaps: z.array(z.string()), // project-level: used but absent from inventory
|
|
308
|
+
distilled: z.array(DistilledSkillSchema), // draft skills distilled from the builtin procedure
|
|
309
|
+
signalSummary: z.object({
|
|
310
|
+
sessionsScanned: z.number(),
|
|
311
|
+
spanDays: z.number(),
|
|
312
|
+
notes: z.array(z.string()),
|
|
313
|
+
}),
|
|
314
|
+
degraded: z.boolean(),
|
|
315
|
+
});
|
|
316
|
+
// Accept a distilled draft → write it to .agentgem/distilled/<name>/SKILL.md.
|
|
317
|
+
export const WorkflowDraftWriteResponseSchema = z.object({ path: z.string() });
|
|
239
318
|
export const GemSchema = z.object({
|
|
240
319
|
name: z.string(),
|
|
241
320
|
createdFrom: z.string(),
|
|
@@ -268,9 +347,10 @@ export const CreateWorkspaceRequestSchema = z.object({
|
|
|
268
347
|
dir: z.string().optional(),
|
|
269
348
|
projects: z.array(z.string()).optional(),
|
|
270
349
|
version: z.string().optional(),
|
|
350
|
+
channels: ChannelDeclSchema,
|
|
271
351
|
});
|
|
272
352
|
export const WorkspaceQuerySchema = z.object({ name: z.string() });
|
|
273
|
-
export const RenderRequestSchema = z.object({ name: z.string(), target: TargetIdSchema });
|
|
353
|
+
export const RenderRequestSchema = z.object({ name: z.string(), target: TargetIdSchema, a2aServer: z.boolean().optional() });
|
|
274
354
|
export const WorkspaceNameRequestSchema = z.object({ name: z.string() });
|
|
275
355
|
export const ListWorkspacesResponseSchema = z.object({ workspaces: z.array(WorkspaceSummarySchema) });
|
|
276
356
|
export const DeleteWorkspaceResponseSchema = z.object({ deleted: z.string() });
|
|
@@ -337,7 +417,8 @@ export const TestbedImportRequestSchema = z.object({
|
|
|
337
417
|
flavor: TestbedFlavorIdSchema.optional(),
|
|
338
418
|
});
|
|
339
419
|
export const ImportedRefSchema = z.object({
|
|
340
|
-
|
|
420
|
+
// mirrors ArtifactType (incl. "channel") — ImportedRef.type is ArtifactType, so this must stay in sync
|
|
421
|
+
type: z.enum(["skill", "mcp_server", "instructions", "hook", "channel"]),
|
|
341
422
|
name: z.string(),
|
|
342
423
|
overwritten: z.boolean(),
|
|
343
424
|
});
|
|
@@ -345,6 +426,74 @@ export const TestbedImportResponseSchema = z.object({
|
|
|
345
426
|
written: z.array(ImportedRefSchema),
|
|
346
427
|
skipped: z.array(z.object({ artifact: z.string(), reason: z.string() })),
|
|
347
428
|
});
|
|
429
|
+
// ── Run a Gem with a local ACP coding agent ──
|
|
430
|
+
export const ToolInvocationSchema = z.object({
|
|
431
|
+
toolCallId: z.string(),
|
|
432
|
+
title: z.string(),
|
|
433
|
+
kind: z.string().optional(),
|
|
434
|
+
status: z.string().optional(),
|
|
435
|
+
});
|
|
436
|
+
export const RunResultSchema = z.object({
|
|
437
|
+
text: z.string(),
|
|
438
|
+
toolCalls: z.array(ToolInvocationSchema),
|
|
439
|
+
});
|
|
440
|
+
export const GemRunOutcomeSchema = z.object({
|
|
441
|
+
ok: z.boolean(),
|
|
442
|
+
error: z.string().optional(),
|
|
443
|
+
result: RunResultSchema,
|
|
444
|
+
sandbox: z.object({ backend: z.string(), isolated: z.boolean() }),
|
|
445
|
+
});
|
|
446
|
+
export const GemExpectationsSchema = z.object({
|
|
447
|
+
expectTools: z.array(z.string()).optional(),
|
|
448
|
+
expectText: z.string().optional(),
|
|
449
|
+
forbidToolFailures: z.boolean().optional(),
|
|
450
|
+
});
|
|
451
|
+
export const VerificationReportSchema = z.object({
|
|
452
|
+
passed: z.boolean(),
|
|
453
|
+
checks: z.array(z.object({ name: z.string(), passed: z.boolean(), detail: z.string() })),
|
|
454
|
+
});
|
|
455
|
+
export const GemRunRequestSchema = z.object({
|
|
456
|
+
// The Gem: either a built selection (like /materialize) or a .gem archive dir.
|
|
457
|
+
selection: GemSelectionSchema.optional(),
|
|
458
|
+
archivePath: z.string().optional(),
|
|
459
|
+
name: z.string().optional(),
|
|
460
|
+
dir: z.string().optional(), // introspect home (selection mode), like /materialize
|
|
461
|
+
projects: z.array(z.string()).optional(),
|
|
462
|
+
task: z.string(),
|
|
463
|
+
// runDir is intentionally NOT accepted from the client: a caller-controlled path is a path-injection
|
|
464
|
+
// sink (and the agent runs there with tool permissions). The server always derives it under
|
|
465
|
+
// AGENTGEM_HOME from the gem name. See gem.controller runGem/prepareGemRun.
|
|
466
|
+
agent: z.enum(["claude", "codex"]).optional(), // which local ACP adapter to drive
|
|
467
|
+
expectations: GemExpectationsSchema.optional(),
|
|
468
|
+
}).refine((d) => d.selection !== undefined || d.archivePath !== undefined, {
|
|
469
|
+
message: "provide either selection or archivePath",
|
|
470
|
+
});
|
|
471
|
+
export const GemRunResponseSchema = z.object({
|
|
472
|
+
dir: z.string(),
|
|
473
|
+
agent: z.string(),
|
|
474
|
+
materialized: TestbedImportResponseSchema,
|
|
475
|
+
run: GemRunOutcomeSchema,
|
|
476
|
+
verification: VerificationReportSchema.optional(),
|
|
477
|
+
});
|
|
478
|
+
// Streaming split: prepare (POST, carries the selection) materializes and hands
|
|
479
|
+
// back an opaque runId; the GET stream then runs it with simple query params.
|
|
480
|
+
export const GemRunPrepareRequestSchema = z.object({
|
|
481
|
+
selection: GemSelectionSchema.optional(),
|
|
482
|
+
archivePath: z.string().optional(),
|
|
483
|
+
name: z.string().optional(),
|
|
484
|
+
dir: z.string().optional(),
|
|
485
|
+
projects: z.array(z.string()).optional(),
|
|
486
|
+
// runDir is intentionally NOT accepted from the client (see GemRunRequestSchema). Server-derived only.
|
|
487
|
+
agent: z.enum(["claude", "codex"]).optional(),
|
|
488
|
+
}).refine((d) => d.selection !== undefined || d.archivePath !== undefined, {
|
|
489
|
+
message: "provide either selection or archivePath",
|
|
490
|
+
});
|
|
491
|
+
export const GemRunPrepareResponseSchema = z.object({
|
|
492
|
+
runId: z.string(),
|
|
493
|
+
runDir: z.string(),
|
|
494
|
+
agent: z.string(),
|
|
495
|
+
materialized: TestbedImportResponseSchema,
|
|
496
|
+
});
|
|
348
497
|
// ── AgentCore deploy (Phase 2) ──
|
|
349
498
|
export const AgentcoreReadyResponseSchema = z.object({ cli: z.boolean(), awsCreds: z.boolean() });
|
|
350
499
|
export const AgentcoreDeployRequestSchema = z.object({ name: z.string() });
|
|
@@ -357,14 +506,35 @@ export const AgentcoreDeployStateSchema = z.object({
|
|
|
357
506
|
// ── Gem Registry ──
|
|
358
507
|
export const RegistryReadyResponseSchema = z.object({ ready: z.boolean() });
|
|
359
508
|
const RegistryItemVersionSchema = z.object({ path: z.string(), gemDigest: z.string(), dependencies: z.array(z.string()) });
|
|
509
|
+
const RegistryItemDiscoverySchema = z.object({
|
|
510
|
+
description: z.string().optional(),
|
|
511
|
+
tags: z.array(z.string()).optional(),
|
|
512
|
+
author: z.string().optional(),
|
|
513
|
+
artifactKinds: z.array(z.string()).optional(),
|
|
514
|
+
updatedAt: z.string().optional(),
|
|
515
|
+
});
|
|
360
516
|
export const RegistryIndexResponseSchema = z.object({
|
|
361
517
|
formatVersion: z.number(),
|
|
362
|
-
items: z.record(z.string(), z.object({ latest: z.string(), versions: z.record(z.string(), RegistryItemVersionSchema) })),
|
|
518
|
+
items: z.record(z.string(), z.object({ latest: z.string(), versions: z.record(z.string(), RegistryItemVersionSchema), discovery: RegistryItemDiscoverySchema.optional() })),
|
|
519
|
+
});
|
|
520
|
+
export const RegistrySearchQuerySchema = z.object({
|
|
521
|
+
q: z.string().optional(),
|
|
522
|
+
kind: z.string().optional(),
|
|
523
|
+
tag: z.string().optional(),
|
|
524
|
+
limit: z.coerce.number().int().positive().max(100).optional(),
|
|
525
|
+
});
|
|
526
|
+
export const RegistrySearchResponseSchema = z.object({
|
|
527
|
+
results: z.array(z.object({
|
|
528
|
+
key: z.string(), latest: z.string(), score: z.number(),
|
|
529
|
+
description: z.string().optional(), tags: z.array(z.string()).optional(),
|
|
530
|
+
author: z.string().optional(), artifactKinds: z.array(z.string()).optional(), updatedAt: z.string().optional(),
|
|
531
|
+
})),
|
|
363
532
|
});
|
|
364
533
|
export const RegistryResolveRequestSchema = z.object({
|
|
365
534
|
refs: z.array(z.string()).min(1),
|
|
366
535
|
mode: z.enum(["materialize", "workspace"]),
|
|
367
536
|
target: TargetIdSchema.optional(),
|
|
537
|
+
a2aServer: z.boolean().optional(),
|
|
368
538
|
});
|
|
369
539
|
const InstallPlanSchema = z.object({
|
|
370
540
|
items: z.array(z.object({ key: z.string(), version: z.string() })),
|
|
@@ -383,6 +553,7 @@ export const RegistryInstallRequestSchema = z.object({
|
|
|
383
553
|
target: TargetIdSchema.optional(),
|
|
384
554
|
dest: z.string().optional(),
|
|
385
555
|
workspaceName: z.string().optional(),
|
|
556
|
+
a2aServer: z.boolean().optional(),
|
|
386
557
|
});
|
|
387
558
|
export const RegistryInstallResponseSchema = z.object({
|
|
388
559
|
plan: InstallPlanSchema,
|
|
@@ -397,6 +568,8 @@ export const RegistryPublishRequestSchema = z.object({
|
|
|
397
568
|
name: z.string().optional(),
|
|
398
569
|
version: z.string(),
|
|
399
570
|
dependencies: z.array(z.string()).optional(),
|
|
571
|
+
description: z.string().optional(), // discovery metadata for search
|
|
572
|
+
tags: z.array(z.string()).optional(),
|
|
400
573
|
});
|
|
401
574
|
export const RegistryPublishResponseSchema = z.object({
|
|
402
575
|
ref: z.string(), version: z.string(), gemDigest: z.string(), commit: z.string(), path: z.string(),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/workflowStream.ts
|
|
2
|
+
//
|
|
3
|
+
// SSE endpoint for the workflow analysis. agentgem's decorator framework returns
|
|
4
|
+
// a single JSON body, so streaming progress (scan → agent token stream → done)
|
|
5
|
+
// is served by a raw Express handler registered on `server.expressApp`. The
|
|
6
|
+
// non-streaming POST /api/workflow/analyze stays for programmatic/test callers.
|
|
7
|
+
import { introspectConfig, introspectProject } from "./gem/introspect.js";
|
|
8
|
+
import { resolveDirs, resolveProject } from "./resolveDir.js";
|
|
9
|
+
import { claudeTranscriptsForCwd, scanWorkflow } from "./gem/workflowScan.js";
|
|
10
|
+
import { recommendWorkflow, recommendationToSelection } from "./gem/acpRecommender.js";
|
|
11
|
+
import { distillWorkflow } from "./gem/distill.js";
|
|
12
|
+
import { transcriptToken, readAnalysisCache, writeAnalysisCache } from "./gem/analysisCache.js";
|
|
13
|
+
export async function streamWorkflowAnalyze(req, res) {
|
|
14
|
+
const root = typeof req.query.root === "string" ? req.query.root : "";
|
|
15
|
+
const dir = typeof req.query.dir === "string" ? req.query.dir : undefined;
|
|
16
|
+
const fresh = req.query.fresh === "1"; // bypass the cache (Re-analyze)
|
|
17
|
+
res.writeHead(200, {
|
|
18
|
+
"Content-Type": "text/event-stream",
|
|
19
|
+
"Cache-Control": "no-cache, no-transform",
|
|
20
|
+
Connection: "keep-alive",
|
|
21
|
+
"X-Accel-Buffering": "no", // disable proxy buffering so events flush immediately
|
|
22
|
+
});
|
|
23
|
+
const send = (event, data) => {
|
|
24
|
+
res.write(`event: ${event}\n`);
|
|
25
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
if (!root) {
|
|
29
|
+
send("failed", { message: "missing root" });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const dirs = resolveDirs(dir);
|
|
33
|
+
const project = introspectProject(resolveProject(root));
|
|
34
|
+
const globalInv = introspectConfig(dirs); // global + plugin artifacts
|
|
35
|
+
const scanInv = { project, global: { skills: globalInv.skills, mcpServers: globalInv.mcpServers, hooks: globalInv.hooks } };
|
|
36
|
+
send("phase", { phase: "scanning" });
|
|
37
|
+
const paths = claudeTranscriptsForCwd(dirs.claudeDir, root);
|
|
38
|
+
// Cache hit (unless Re-analyze): return the prior result instantly so the
|
|
39
|
+
// user can revisit a project to pick another candidate without re-running
|
|
40
|
+
// the agent. Token invalidates when sessions are added/updated.
|
|
41
|
+
const token = transcriptToken(paths);
|
|
42
|
+
if (!fresh) {
|
|
43
|
+
const cached = readAnalysisCache(root, token);
|
|
44
|
+
if (cached) {
|
|
45
|
+
send("done", { ...cached, cached: true });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const signal = scanWorkflow(paths, scanInv, { retainSequences: true });
|
|
50
|
+
send("phase", { phase: "scanned", transcripts: paths.length, sessions: signal.sessions.scanned });
|
|
51
|
+
send("phase", { phase: "thinking" });
|
|
52
|
+
// Selective recommendation streams its tokens; distillation runs concurrently
|
|
53
|
+
// but SILENT — two delta streams would interleave and garble the display
|
|
54
|
+
// (proposal §5). Both never throw, so wall-clock is max(...), not the sum.
|
|
55
|
+
const [{ analysis, degraded }, distill] = await Promise.all([
|
|
56
|
+
recommendWorkflow(signal, scanInv, { onDelta: (chunk) => send("delta", { text: chunk }) }),
|
|
57
|
+
distillWorkflow(signal, scanInv),
|
|
58
|
+
]);
|
|
59
|
+
send("phase", { phase: "validating" });
|
|
60
|
+
const candidates = analysis.candidates.map((c) => ({ ...c, selection: recommendationToSelection(c) }));
|
|
61
|
+
const payload = {
|
|
62
|
+
candidates,
|
|
63
|
+
gaps: analysis.gaps,
|
|
64
|
+
distilled: distill.distilled,
|
|
65
|
+
signalSummary: { sessionsScanned: signal.sessions.scanned, spanDays: signal.sessions.spanDays, notes: signal.notes },
|
|
66
|
+
degraded,
|
|
67
|
+
};
|
|
68
|
+
if (!degraded)
|
|
69
|
+
writeAnalysisCache(root, token, payload, Date.now()); // don't cache fallbacks
|
|
70
|
+
send("done", { ...payload, cached: false });
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
send("failed", { message: err?.message ?? String(err) });
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
res.end();
|
|
77
|
+
}
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ninemind/agentgem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A local web UI that introspects your coding-agent config, redacts secrets at capture, and builds a portable, composable Gem.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"type": "module",
|
|
40
40
|
"packageManager": "pnpm@10.29.2",
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "tsc -b &&
|
|
42
|
+
"build": "tsc -b && node scripts/copy-public.mjs",
|
|
43
43
|
"dev": "pnpm build && node dist/index.js",
|
|
44
44
|
"start": "node dist/index.js",
|
|
45
45
|
"build:site": "node website/build.mjs",
|
|
@@ -54,17 +54,22 @@
|
|
|
54
54
|
"@agentback/openapi": "^0.5.2",
|
|
55
55
|
"@agentback/rest": "^0.5.2",
|
|
56
56
|
"@agentback/rest-explorer": "^0.5.2",
|
|
57
|
+
"@agentclientprotocol/sdk": "^0.28.1",
|
|
57
58
|
"@anthropic-ai/sdk": "^0.104.2",
|
|
58
59
|
"@aws-sdk/client-bedrock-agentcore-control": "^3.1073.0",
|
|
59
60
|
"dotenv": "^17.4.2",
|
|
60
61
|
"tslib": "^2.8.1",
|
|
62
|
+
"undici": "^6.27.0",
|
|
61
63
|
"vercel": "^54.14.2",
|
|
62
64
|
"zod": "^4.4.3"
|
|
63
65
|
},
|
|
64
66
|
"devDependencies": {
|
|
65
67
|
"@types/node": "^24",
|
|
66
68
|
"@types/supertest": "^6",
|
|
69
|
+
"eve": "^0.15.0",
|
|
70
|
+
"highlight.js": "^11.11.1",
|
|
67
71
|
"marked": "^18.0.5",
|
|
72
|
+
"marked-highlight": "^2.2.4",
|
|
68
73
|
"supertest": "^7",
|
|
69
74
|
"typescript": "^6",
|
|
70
75
|
"vitest": "^3"
|