@ninemind/agentgem 0.2.0 → 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/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,25 +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
222
  a2aServer: z.boolean().optional(), // a2a target: also emit the runnable server, not just the Agent Card
182
- }).refine((d) => d.selection !== undefined || d.archivePath !== undefined, {
183
- message: "provide either selection or archivePath",
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",
184
226
  });
185
227
  export const DeployTargetIdSchema = z.enum(deployTargetIds);
186
228
  export const DeployReadyQuerySchema = z.object({ target: DeployTargetIdSchema.optional() });
@@ -194,6 +236,7 @@ export const PublishPreviewRequestSchema = z.object({
194
236
  dir: z.string().optional(),
195
237
  projects: z.array(z.string()).optional(),
196
238
  target: DeployTargetIdSchema.optional(),
239
+ channels: ChannelDeclSchema,
197
240
  });
198
241
  export const PublishRequestSchema = PublishPreviewRequestSchema.extend({ requestId: z.string().min(8).max(128), wsName: z.string().optional() });
199
242
  const ManagedAgentPayloadSchema = z.object({
@@ -243,7 +286,8 @@ export const WorkflowAnalyzeRequestSchema = z.object({
243
286
  root: z.string(), // the project root to analyze (one of the discovered cwds)
244
287
  });
245
288
  const RecommendedItemSchema = z.object({
246
- type: z.enum(["skill", "mcp_server", "instructions", "hook"]),
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"]),
247
291
  name: z.string(),
248
292
  reason: z.string(),
249
293
  root: z.string().nullable(), // project root, or null for a global/plugin artifact
@@ -261,6 +305,7 @@ const GemCandidateSchema = z.object({
261
305
  export const WorkflowAnalyzeResponseSchema = z.object({
262
306
  candidates: z.array(GemCandidateSchema),
263
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
264
309
  signalSummary: z.object({
265
310
  sessionsScanned: z.number(),
266
311
  spanDays: z.number(),
@@ -268,6 +313,8 @@ export const WorkflowAnalyzeResponseSchema = z.object({
268
313
  }),
269
314
  degraded: z.boolean(),
270
315
  });
316
+ // Accept a distilled draft → write it to .agentgem/distilled/<name>/SKILL.md.
317
+ export const WorkflowDraftWriteResponseSchema = z.object({ path: z.string() });
271
318
  export const GemSchema = z.object({
272
319
  name: z.string(),
273
320
  createdFrom: z.string(),
@@ -300,9 +347,10 @@ export const CreateWorkspaceRequestSchema = z.object({
300
347
  dir: z.string().optional(),
301
348
  projects: z.array(z.string()).optional(),
302
349
  version: z.string().optional(),
350
+ channels: ChannelDeclSchema,
303
351
  });
304
352
  export const WorkspaceQuerySchema = z.object({ name: z.string() });
305
- 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() });
306
354
  export const WorkspaceNameRequestSchema = z.object({ name: z.string() });
307
355
  export const ListWorkspacesResponseSchema = z.object({ workspaces: z.array(WorkspaceSummarySchema) });
308
356
  export const DeleteWorkspaceResponseSchema = z.object({ deleted: z.string() });
@@ -369,7 +417,8 @@ export const TestbedImportRequestSchema = z.object({
369
417
  flavor: TestbedFlavorIdSchema.optional(),
370
418
  });
371
419
  export const ImportedRefSchema = z.object({
372
- type: z.enum(["skill", "mcp_server", "instructions", "hook"]),
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"]),
373
422
  name: z.string(),
374
423
  overwritten: z.boolean(),
375
424
  });
@@ -377,6 +426,74 @@ export const TestbedImportResponseSchema = z.object({
377
426
  written: z.array(ImportedRefSchema),
378
427
  skipped: z.array(z.object({ artifact: z.string(), reason: z.string() })),
379
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
+ });
380
497
  // ── AgentCore deploy (Phase 2) ──
381
498
  export const AgentcoreReadyResponseSchema = z.object({ cli: z.boolean(), awsCreds: z.boolean() });
382
499
  export const AgentcoreDeployRequestSchema = z.object({ name: z.string() });
@@ -389,14 +506,35 @@ export const AgentcoreDeployStateSchema = z.object({
389
506
  // ── Gem Registry ──
390
507
  export const RegistryReadyResponseSchema = z.object({ ready: z.boolean() });
391
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
+ });
392
516
  export const RegistryIndexResponseSchema = z.object({
393
517
  formatVersion: z.number(),
394
- 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
+ })),
395
532
  });
396
533
  export const RegistryResolveRequestSchema = z.object({
397
534
  refs: z.array(z.string()).min(1),
398
535
  mode: z.enum(["materialize", "workspace"]),
399
536
  target: TargetIdSchema.optional(),
537
+ a2aServer: z.boolean().optional(),
400
538
  });
401
539
  const InstallPlanSchema = z.object({
402
540
  items: z.array(z.object({ key: z.string(), version: z.string() })),
@@ -415,6 +553,7 @@ export const RegistryInstallRequestSchema = z.object({
415
553
  target: TargetIdSchema.optional(),
416
554
  dest: z.string().optional(),
417
555
  workspaceName: z.string().optional(),
556
+ a2aServer: z.boolean().optional(),
418
557
  });
419
558
  export const RegistryInstallResponseSchema = z.object({
420
559
  plan: InstallPlanSchema,
@@ -429,6 +568,8 @@ export const RegistryPublishRequestSchema = z.object({
429
568
  name: z.string().optional(),
430
569
  version: z.string(),
431
570
  dependencies: z.array(z.string()).optional(),
571
+ description: z.string().optional(), // discovery metadata for search
572
+ tags: z.array(z.string()).optional(),
432
573
  });
433
574
  export const RegistryPublishResponseSchema = z.object({
434
575
  ref: z.string(), version: z.string(), gemDigest: z.string(), commit: z.string(), path: z.string(),
@@ -8,6 +8,7 @@ import { introspectConfig, introspectProject } from "./gem/introspect.js";
8
8
  import { resolveDirs, resolveProject } from "./resolveDir.js";
9
9
  import { claudeTranscriptsForCwd, scanWorkflow } from "./gem/workflowScan.js";
10
10
  import { recommendWorkflow, recommendationToSelection } from "./gem/acpRecommender.js";
11
+ import { distillWorkflow } from "./gem/distill.js";
11
12
  import { transcriptToken, readAnalysisCache, writeAnalysisCache } from "./gem/analysisCache.js";
12
13
  export async function streamWorkflowAnalyze(req, res) {
13
14
  const root = typeof req.query.root === "string" ? req.query.root : "";
@@ -45,17 +46,22 @@ export async function streamWorkflowAnalyze(req, res) {
45
46
  return;
46
47
  }
47
48
  }
48
- const signal = scanWorkflow(paths, scanInv);
49
+ const signal = scanWorkflow(paths, scanInv, { retainSequences: true });
49
50
  send("phase", { phase: "scanned", transcripts: paths.length, sessions: signal.sessions.scanned });
50
51
  send("phase", { phase: "thinking" });
51
- const { analysis, degraded } = await recommendWorkflow(signal, scanInv, {
52
- onDelta: (chunk) => send("delta", { text: chunk }),
53
- });
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
+ ]);
54
59
  send("phase", { phase: "validating" });
55
60
  const candidates = analysis.candidates.map((c) => ({ ...c, selection: recommendationToSelection(c) }));
56
61
  const payload = {
57
62
  candidates,
58
63
  gaps: analysis.gaps,
64
+ distilled: distill.distilled,
59
65
  signalSummary: { sessionsScanned: signal.sessions.scanned, spanDays: signal.sessions.spanDays, notes: signal.notes },
60
66
  degraded,
61
67
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ninemind/agentgem",
3
- "version": "0.2.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 && mkdir -p dist/public && cp src/public/index.html dist/public/index.html",
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",
@@ -59,13 +59,17 @@
59
59
  "@aws-sdk/client-bedrock-agentcore-control": "^3.1073.0",
60
60
  "dotenv": "^17.4.2",
61
61
  "tslib": "^2.8.1",
62
+ "undici": "^6.27.0",
62
63
  "vercel": "^54.14.2",
63
64
  "zod": "^4.4.3"
64
65
  },
65
66
  "devDependencies": {
66
67
  "@types/node": "^24",
67
68
  "@types/supertest": "^6",
69
+ "eve": "^0.15.0",
70
+ "highlight.js": "^11.11.1",
68
71
  "marked": "^18.0.5",
72
+ "marked-highlight": "^2.2.4",
69
73
  "supertest": "^7",
70
74
  "typescript": "^6",
71
75
  "vitest": "^3"