@aiwg/cli 2026.10.9 → 2026.10.11

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.
Files changed (116) hide show
  1. package/agentic/code/providers/model-capabilities.v1.json +275 -17
  2. package/bin/aiwg.mjs +8 -0
  3. package/dist/src/cli/handlers/mc-admission.js +39 -0
  4. package/dist/src/cli/handlers/mc.js +2 -31
  5. package/dist/src/cli/handlers/models-help.js +52 -0
  6. package/dist/src/cli/handlers/models.js +11 -81
  7. package/dist/src/cli/handlers/sessions.js +198 -15
  8. package/dist/src/cli/handlers/use-addon-profile.js +72 -0
  9. package/dist/src/cli/handlers/use.js +203 -234
  10. package/dist/src/cli/ui.js +1 -0
  11. package/dist/src/config/aiwg-config.js +38 -184
  12. package/dist/src/config/cli-help.js +54 -0
  13. package/dist/src/config/cli.js +5 -69
  14. package/dist/src/config/config-normalization.js +10 -0
  15. package/dist/src/config/worker-activation-gate.js +26 -0
  16. package/dist/src/config/worker-adoption-cli.js +102 -0
  17. package/dist/src/config/worker-adoption.js +368 -0
  18. package/dist/src/config/worker-config-digest.js +14 -0
  19. package/dist/src/config/worker-context.js +337 -0
  20. package/dist/src/config/worker-group-templates.js +150 -0
  21. package/dist/src/config/worker-guidance-warning.js +28 -0
  22. package/dist/src/config/worker-owner-lock.js +152 -0
  23. package/dist/src/config/worker-parallelism.js +154 -0
  24. package/dist/src/config/worker-write.js +75 -0
  25. package/dist/src/decision/telemetry/exporter.js +2 -1
  26. package/dist/src/decision/telemetry/index.js +1 -0
  27. package/dist/src/decision/telemetry/mapping.js +43 -0
  28. package/dist/src/decision/telemetry/otlp-http.js +5 -2
  29. package/dist/src/decision/telemetry/redaction.js +6 -1
  30. package/dist/src/decision/telemetry/types.js +3 -0
  31. package/dist/src/decision/telemetry/worker.js +69 -0
  32. package/dist/src/lint/rule-activation.js +15 -7
  33. package/dist/src/lint/runner.js +2 -2
  34. package/dist/src/mcp/server.mjs +2 -11
  35. package/dist/src/mcp/stdio-lifecycle.mjs +27 -0
  36. package/dist/src/mcp/tools/worker-failure-diagnostics.mjs +9 -0
  37. package/dist/src/mcp/tools/worker-invoke.mjs +114 -16
  38. package/dist/src/mission-protocol/codecs.js +8 -38
  39. package/dist/src/mission-protocol/worker-contract-diagnostics.js +62 -0
  40. package/dist/src/mission-protocol/worker-invocation-types.js +2 -0
  41. package/dist/src/models/conductor-data-policy.d.mts +8 -0
  42. package/dist/src/models/conductor-data-policy.mjs +190 -0
  43. package/dist/src/models/config-loader.js +19 -3
  44. package/dist/src/models/config-writer.js +27 -0
  45. package/dist/src/models/model-capabilities.v1.json +275 -17
  46. package/dist/src/models/provider-policy.js +14 -155
  47. package/dist/src/models/resolver.js +2 -293
  48. package/dist/src/models/worker-artifact-binding.mjs +12 -0
  49. package/dist/src/models/worker-direct-readiness-policy.js +155 -0
  50. package/dist/src/models/worker-direct-readiness.js +9 -218
  51. package/dist/src/models/worker-health-readiness.js +105 -26
  52. package/dist/src/models/worker-installation.mjs +12 -14
  53. package/dist/src/models/worker-member-references.js +64 -0
  54. package/dist/src/models/worker-plan.js +317 -0
  55. package/dist/src/models/worker-selection-policy.js +217 -0
  56. package/dist/src/models/wrapper-deployment.js +1 -1
  57. package/dist/src/policy/authorization.js +29 -1
  58. package/dist/src/process/aiwg-probe-command.d.mts +15 -0
  59. package/dist/src/process/aiwg-probe-command.mjs +37 -0
  60. package/dist/src/serve/shared-host-admission-checks.js +116 -0
  61. package/dist/src/serve/shared-host-scheduler.js +25 -107
  62. package/dist/src/serve/shared-host-starvation.js +73 -0
  63. package/dist/src/serve/worker-artifacts.d.mts +4 -0
  64. package/dist/src/serve/worker-artifacts.mjs +56 -0
  65. package/dist/src/serve/worker-command-request.js +68 -0
  66. package/dist/src/serve/worker-invocation-paths.js +144 -0
  67. package/dist/src/serve/worker-invocation-preparation.js +169 -0
  68. package/dist/src/serve/worker-invocation.js +24 -291
  69. package/dist/src/serve/worker-stdin-frame.d.mts +2 -2
  70. package/dist/src/serve/worker-stdin-frame.mjs +6 -6
  71. package/dist/src/sessions/adapters/grokbot.js +7 -0
  72. package/dist/src/sessions/batch-import.js +34 -6
  73. package/dist/src/sessions/contracts.js +19 -0
  74. package/dist/src/sessions/coverage-preflight.js +7 -1
  75. package/dist/src/sessions/fortemi-export-mapping.js +33 -3
  76. package/dist/src/sessions/host-catalog.js +157 -0
  77. package/dist/src/sessions/image-blobs.js +141 -0
  78. package/dist/src/sessions/importer.js +23 -3
  79. package/dist/src/sessions/index.js +2 -0
  80. package/dist/src/sessions/refresh.js +25 -2
  81. package/dist/src/sessions/repository.js +164 -22
  82. package/dist/src/sessions/workspace-discovery.js +5 -2
  83. package/dist/src/smiths/context-pipeline/parallelism-section.js +19 -7
  84. package/package.json +15 -1
  85. package/schemas/mission-v1.schema.json +20 -19
  86. package/schemas/models/model-config-compatibility.v1.schema.json +16 -0
  87. package/tools/agents/providers/claude-post-deploy.mjs +35 -0
  88. package/tools/agents/providers/claude.mjs +3 -30
  89. package/tools/agents/providers/codex-post-deploy.mjs +13 -0
  90. package/tools/agents/providers/codex.mjs +2 -8
  91. package/tools/agents/providers/conductor-data-guard.mjs +61 -0
  92. package/tools/agents/providers/rule-delivery.mjs +3 -16
  93. package/tools/agents/providers/rule-presentation.mjs +38 -0
  94. package/tools/plugin/package-plugins.mjs +1 -1
  95. package/tools/ralph-external/lib/claude-adapter.mjs +2 -2
  96. package/tools/ralph-external/lib/codex-adapter.mjs +3 -3
  97. package/tools/ralph-external/lib/managed-worker-attempt.mjs +86 -0
  98. package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
  99. package/tools/ralph-external/lib/provider-adapter.mjs +11 -260
  100. package/tools/ralph-external/lib/session-attempt.mjs +85 -0
  101. package/tools/ralph-external/lib/strict-worker-json.mjs +190 -0
  102. package/tools/ralph-external/lib/worker-artifacts.mjs +209 -0
  103. package/tools/ralph-external/lib/worker-context.mjs +237 -0
  104. package/tools/ralph-external/lib/worker-final.mjs +143 -0
  105. package/tools/ralph-external/lib/worker-inputs.mjs +55 -0
  106. package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
  107. package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
  108. package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
  109. package/tools/ralph-external/lib/worker-isolation.mjs +19 -73
  110. package/tools/ralph-external/lib/worker-launch-cli.mjs +49 -0
  111. package/tools/ralph-external/lib/worker-native-sandbox.mjs +83 -0
  112. package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
  113. package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
  114. package/tools/ralph-external/lib/worker-termination.mjs +82 -0
  115. package/tools/ralph-external/session-launcher.mjs +124 -313
  116. package/vscode-extension/schemas/aiwg.config.v1.json +161 -2
@@ -2,8 +2,10 @@ import { requireFeaturePackage } from '../features/runtime.js';
2
2
  import { CandidateReviewReceiptSchema, DeletionReceiptSchema, IntelligenceCandidateSchema, PromotionDependencyDecisionSchema, PromotionReceiptSchema, SessionContractError, sha256, } from './contracts.js';
3
3
  import { coverageFromBatchRun, } from './batch-contracts.js';
4
4
  import { redactSessionText, redactSessionValue, sanitizeSessionEvent, sanitizeNativeExtensions } from './policy.js';
5
+ import { extractBatchEventImages, isImageDigest, } from './image-blobs.js';
5
6
  import { effectiveSessionLifecycle } from './lifecycle.js';
6
7
  import { deriveSessionAnalytics, } from './analytics.js';
8
+ import { HOST_REFERENCE_TABLE_DDL, } from './host-catalog.js';
7
9
  const POLICY_PROVIDER_MIGRATION = 'policy-provider-identity:v2';
8
10
  const EVENT_ORIGIN_INTENT_MIGRATION = 'event-origin-intent:v1';
9
11
  const VISIBLE_SESSION_PREDICATE = `s.lifecycle != 'tombstoned' AND EXISTS (
@@ -114,6 +116,17 @@ export class SessionRepository {
114
116
  CREATE TABLE IF NOT EXISTS session_catalog_meta (
115
117
  key TEXT PRIMARY KEY, value TEXT NOT NULL
116
118
  );
119
+ -- Content-addressed tool-output image bytes (#3265). One row per
120
+ -- content hash per catalog: repeat imports of identical images share
121
+ -- the row (INSERT OR IGNORE). Events reference blobs by digest and
122
+ -- keep only a bracketed image placeholder (sha256, type, size) in
123
+ -- searchableText, so image bytes never reach FTS. Pre-#3265 rows with
124
+ -- inline bytes are left untouched (no destructive migration).
125
+ CREATE TABLE IF NOT EXISTS session_image_blobs (
126
+ digest TEXT PRIMARY KEY, media_type TEXT NOT NULL,
127
+ byte_length INTEGER NOT NULL, source_locator TEXT,
128
+ source_offset INTEGER, bytes BLOB NOT NULL
129
+ );
117
130
  CREATE TABLE IF NOT EXISTS session_analytics_facts (
118
131
  fact_id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL,
119
132
  session_id TEXT NOT NULL, event_id TEXT NOT NULL,
@@ -144,6 +157,9 @@ export class SessionRepository {
144
157
  -- alignment without reading searchable text or event JSON.
145
158
  CREATE INDEX IF NOT EXISTS idx_fts_event_id ON session_event_fts_content(c0);
146
159
  `);
160
+ // Workspace pointers at account-scoped sessions stored in the host
161
+ // catalog (#3232). References, never copies; legacy copied rows stay.
162
+ this.db.exec(HOST_REFERENCE_TABLE_DDL);
147
163
  const deletionReceiptColumns = this.db.prepare('PRAGMA table_info(deletion_receipts)').all().map((row) => String(row.name));
148
164
  if (!deletionReceiptColumns.includes('workspace_id')) {
149
165
  this.db.exec('ALTER TABLE deletion_receipts ADD COLUMN workspace_id TEXT');
@@ -479,6 +495,18 @@ export class SessionRepository {
479
495
  };
480
496
  }
481
497
  applyImport(batch, checkpoint, publish = true) {
498
+ // Lift inline image payloads out of event text before redaction (#3265):
499
+ // base64 data URLs in text survive redaction and would otherwise be
500
+ // stored inline. Provider image blocks in extensions keep their existing
501
+ // redaction to markers; their bytes are not retained.
502
+ // Idempotent -- placeholder text from an earlier pass matches no image
503
+ // pattern, so importer-extracted batches and direct writers converge.
504
+ // Bytes are parked beside the batch: the redaction pass below walks
505
+ // every value and would flatten Uint8Array into plain objects.
506
+ const extracted = extractBatchEventImages(batch);
507
+ const imageBlobs = extracted.imageBlobs;
508
+ batch = { ...batch, events: extracted.events };
509
+ delete batch.imageBlobs;
482
510
  // Guard callers that submit normalized batches directly as well as adapters.
483
511
  batch = redactSessionValue({
484
512
  ...batch,
@@ -525,6 +553,17 @@ export class SessionRepository {
525
553
  const merged = mergeSessionAggregate(prior, session);
526
554
  this.db.prepare(`UPDATE sessions SET source_digest=?, lifecycle=?, data=? WHERE session_id=?`).run(merged.sourceDigest, merged.lifecycle, JSON.stringify(merged), merged.sessionId);
527
555
  }
556
+ // Persist image bytes once per content hash per catalog (#3265).
557
+ // INSERT OR IGNORE keeps the first-seen row when identical images
558
+ // arrive in later batches or replayed records.
559
+ const insertImageBlob = this.db.prepare(`INSERT OR IGNORE INTO session_image_blobs
560
+ (digest, media_type, byte_length, source_locator, source_offset, bytes)
561
+ VALUES (?, ?, ?, ?, ?, ?)`);
562
+ for (const image of imageBlobs) {
563
+ if (!isImageDigest(image.digest))
564
+ continue;
565
+ insertImageBlob.run(image.digest, image.mediaType, image.byteLength, image.locatorClass ?? null, image.offset ?? null, Buffer.from(image.bytes));
566
+ }
528
567
  const insertEvent = this.db.prepare(`INSERT OR IGNORE INTO session_events
529
568
  (event_id, session_id, source_id, import_run_id, sequence_no, digest, data)
530
569
  VALUES (?, ?, ?, ?, ?, ?, ?)`);
@@ -746,6 +785,49 @@ export class SessionRepository {
746
785
  listWorkspaceIds() {
747
786
  return this.db.prepare('SELECT DISTINCT workspace_id FROM sessions ORDER BY workspace_id').all().map((row) => String(row.workspace_id));
748
787
  }
788
+ /**
789
+ * Record a workspace pointer at an account-scoped session stored in the
790
+ * host catalog (#3232). Non-destructive: existing copied rows are left
791
+ * untouched and remain readable.
792
+ */
793
+ recordHostSessionReference(reference) {
794
+ this.db.prepare(`INSERT INTO session_host_refs
795
+ (provider, native_session_id, session_id, source_id, host_catalog)
796
+ VALUES (?, ?, ?, ?, ?)
797
+ ON CONFLICT(provider, native_session_id) DO UPDATE SET
798
+ session_id=excluded.session_id, source_id=excluded.source_id,
799
+ host_catalog=excluded.host_catalog`).run(reference.provider, reference.nativeSessionId, reference.sessionId, reference.sourceId, reference.hostCatalog);
800
+ }
801
+ listHostSessionReferences() {
802
+ return this.db.prepare(`SELECT provider, native_session_id, session_id, source_id, host_catalog
803
+ FROM session_host_refs ORDER BY provider, native_session_id`).all().map((row) => ({
804
+ provider: String(row.provider),
805
+ nativeSessionId: String(row.native_session_id),
806
+ sessionId: String(row.session_id),
807
+ sourceId: String(row.source_id),
808
+ hostCatalog: String(row.host_catalog),
809
+ }));
810
+ }
811
+ /**
812
+ * Visible sessions reduced to aggregate identities (plus the internal ids
813
+ * reference recording needs). Aggregate search/analytics over several
814
+ * catalogs dedupe on provider + native session so legacy copied rows
815
+ * count once (#3232).
816
+ */
817
+ listSessionIdentities(workspaceId) {
818
+ const where = workspaceId
819
+ ? `s.workspace_id=? AND ${VISIBLE_SESSION_PREDICATE}`
820
+ : VISIBLE_SESSION_PREDICATE;
821
+ const params = workspaceId ? [workspaceId] : [];
822
+ return this.db.prepare(`SELECT json_extract(s.data, '$.provider') AS provider, s.native_session_id,
823
+ s.session_id, s.source_id
824
+ FROM sessions s WHERE ${where} ORDER BY s.session_id`).all(...params).map((row) => ({
825
+ provider: String(row.provider),
826
+ nativeSessionId: String(row.native_session_id),
827
+ sessionId: String(row.session_id),
828
+ sourceId: String(row.source_id),
829
+ }));
830
+ }
749
831
  saveBatchImportRun(run) {
750
832
  run = redactSessionValue(run).value;
751
833
  const save = this.db.transaction(() => {
@@ -889,6 +971,62 @@ export class SessionRepository {
889
971
  ORDER BY e.sequence_no, e.event_id`).all(...(workspaceId ? [sessionId, workspaceId] : [sessionId]))
890
972
  .map((row) => JSON.parse(String(row.data)));
891
973
  }
974
+ /**
975
+ * Distinct image blobs in this catalog. Identical images share one row
976
+ * per content hash, so this counts unique images, not references (#3265).
977
+ */
978
+ countImageBlobs() {
979
+ return Number(this.db.prepare('SELECT COUNT(*) AS count FROM session_image_blobs').get()?.count ?? 0);
980
+ }
981
+ /**
982
+ * Fetch one image blob by content digest. Returns null when the blob is
983
+ * unknown or the digest is malformed -- callers report that as an
984
+ * "unavailable" reference rather than failing the whole read (#3265).
985
+ * Pre-#3265 rows carry inline bytes instead of references and never reach
986
+ * this lookup; they load verbatim through `listEvents`.
987
+ */
988
+ getImageBlob(digest) {
989
+ if (!isImageDigest(digest))
990
+ return null;
991
+ const row = this.db.prepare(`SELECT digest, media_type, byte_length, source_locator, source_offset, bytes
992
+ FROM session_image_blobs WHERE digest=?`).get(digest);
993
+ if (!row)
994
+ return null;
995
+ return {
996
+ digest: String(row.digest),
997
+ mediaType: String(row.media_type),
998
+ byteLength: Number(row.byte_length),
999
+ sourceLocator: row.source_locator === null ? null : String(row.source_locator),
1000
+ sourceOffset: row.source_offset === null ? null : Number(row.source_offset),
1001
+ bytes: Uint8Array.from(row.bytes),
1002
+ };
1003
+ }
1004
+ /**
1005
+ * Resolve an event's image references against the blob store. Entries
1006
+ * whose bytes are present resolve `available`; entries whose blob is
1007
+ * missing (pruned catalog, legacy reference) resolve `unavailable` with
1008
+ * the reference metadata intact so callers can report it (#3265). Events
1009
+ * without references -- including pre-#3265 inline rows -- resolve to [].
1010
+ */
1011
+ resolveEventImages(event) {
1012
+ return (event.imageRefs ?? []).map((ref) => {
1013
+ const stored = this.getImageBlob(ref.digest);
1014
+ if (!stored)
1015
+ return { ...ref, status: 'unavailable' };
1016
+ return {
1017
+ digest: ref.digest,
1018
+ mediaType: stored.mediaType,
1019
+ byteLength: stored.byteLength,
1020
+ ...(ref.locatorClass !== undefined
1021
+ ? { locatorClass: ref.locatorClass }
1022
+ : stored.sourceLocator !== null ? { locatorClass: stored.sourceLocator } : {}),
1023
+ ...(ref.offset !== undefined
1024
+ ? { offset: ref.offset }
1025
+ : stored.sourceOffset !== null ? { offset: stored.sourceOffset } : {}),
1026
+ status: 'available',
1027
+ };
1028
+ });
1029
+ }
892
1030
  listTimelineInputs(workspaceId) {
893
1031
  return this.db.prepare(`SELECT s.data AS session_data, e.data AS event_data
894
1032
  FROM session_events e
@@ -1168,28 +1306,7 @@ export class SessionRepository {
1168
1306
  }
1169
1307
  analyticsSummary(options) {
1170
1308
  const facts = this.listAnalyticsFacts({ ...options, limit: options.limit ?? 5_000 });
1171
- const byCategory = countBy(facts, (fact) => fact.category);
1172
- const byStatus = countBy(facts, (fact) => fact.status);
1173
- const byProvider = countBy(facts, (fact) => fact.provider);
1174
- const byTool = countBy(facts.filter((fact) => fact.toolName), (fact) => fact.toolName);
1175
- return {
1176
- analyticsVersion: '1.0.0',
1177
- workspaceId: options.workspaceId,
1178
- totals: {
1179
- facts: facts.length,
1180
- sessions: new Set(facts.map((fact) => fact.sessionId)).size,
1181
- toolCalls: facts.filter((fact) => fact.category === 'tool-call').length,
1182
- toolFailures: facts.filter((fact) => fact.category === 'tool-result' && fact.status === 'failed').length,
1183
- escalations: facts.filter((fact) => fact.category === 'escalation').length,
1184
- hitlDecisions: facts.filter((fact) => fact.category === 'hitl').length,
1185
- indicators: facts.filter((fact) => fact.category === 'indicator').length,
1186
- retryGroups: new Set(facts.map((fact) => fact.retryGroupId).filter(Boolean)).size,
1187
- },
1188
- byCategory,
1189
- byStatus,
1190
- byProvider,
1191
- byTool,
1192
- };
1309
+ return summarizeSessionAnalyticsFacts(facts, options.workspaceId);
1193
1310
  }
1194
1311
  getAnalyticsEvidence(id, workspaceId) {
1195
1312
  const row = this.db.prepare(`SELECT f.data AS fact_data, e.data AS event_data
@@ -1852,6 +1969,31 @@ function canonicalDevinLocator(value) {
1852
1969
  ? `devin-desktop-${value.slice('windsurf-'.length)}`
1853
1970
  : value;
1854
1971
  }
1972
+ /**
1973
+ * Summarize analytics facts without touching a catalog, so cross-catalog
1974
+ * merges (workspace facts plus referenced host facts, #3232) report the
1975
+ * same shape as a single-catalog summary.
1976
+ */
1977
+ export function summarizeSessionAnalyticsFacts(facts, workspaceId) {
1978
+ return {
1979
+ analyticsVersion: '1.0.0',
1980
+ workspaceId,
1981
+ totals: {
1982
+ facts: facts.length,
1983
+ sessions: new Set(facts.map((fact) => fact.sessionId)).size,
1984
+ toolCalls: facts.filter((fact) => fact.category === 'tool-call').length,
1985
+ toolFailures: facts.filter((fact) => fact.category === 'tool-result' && fact.status === 'failed').length,
1986
+ escalations: facts.filter((fact) => fact.category === 'escalation').length,
1987
+ hitlDecisions: facts.filter((fact) => fact.category === 'hitl').length,
1988
+ indicators: facts.filter((fact) => fact.category === 'indicator').length,
1989
+ retryGroups: new Set(facts.map((fact) => fact.retryGroupId).filter(Boolean)).size,
1990
+ },
1991
+ byCategory: countBy(facts, (fact) => fact.category),
1992
+ byStatus: countBy(facts, (fact) => fact.status),
1993
+ byProvider: countBy(facts, (fact) => fact.provider),
1994
+ byTool: countBy(facts.filter((fact) => fact.toolName), (fact) => fact.toolName),
1995
+ };
1996
+ }
1855
1997
  function countBy(items, key) {
1856
1998
  const counts = {};
1857
1999
  for (const item of items) {
@@ -269,10 +269,13 @@ export async function discoverWorkspaceHistories(options) {
269
269
  // old entries, so a content-digest sourceId (the default above)
270
270
  // would churn every re-discovery and break checkpoint continuity.
271
271
  // Key it on the conversation id instead, same rationale as the OMP
272
- // override above.
272
+ // override above. Unlike OMP, the key carries no workspaceId: Grok
273
+ // Bot conversations are account-scoped (#3232), so the same replica
274
+ // must resolve to one host-catalog source no matter which workspace
275
+ // authorizes it; workspace catalogs keep a reference instead of a copy.
273
276
  const conversationId = grokbotConversationIdFromLocator(locator);
274
277
  if (conversationId) {
275
- source.sourceId = sha256(['grokbot-native-source-v1', workspaceId, conversationId].join('\0'));
278
+ source.sourceId = sha256(['grokbot-native-source-v1', conversationId].join('\0'));
276
279
  }
277
280
  }
278
281
  providerSources.push(source);
@@ -87,7 +87,7 @@ export const MAX_SHOWN_GROUPS = 8;
87
87
  */
88
88
  function excludedMembers(parallelism, group) {
89
89
  const excluded = [];
90
- for (const member of group.members) {
90
+ for (const member of (group.members ?? [])) {
91
91
  if (member.enabled === false)
92
92
  excluded.push(`${member.id} (member disabled)`);
93
93
  else if (parallelism?.clis?.[member.cli]?.enabled === false)
@@ -97,15 +97,19 @@ function excludedMembers(parallelism, group) {
97
97
  }
98
98
  function groupSummaries(entries, parallelism) {
99
99
  return entries.map(([id, group]) => {
100
- const roles = [...new Set(group.members.flatMap(m => m.roles ?? []))].join('/');
101
- const clis = [...new Set(group.members.map(m => m.cli))].join('/');
102
- const parts = [`${oneLine(id, 64)} (members: ${group.members.length}`];
100
+ const roles = [...new Set((group.members ?? []).flatMap(m => m.roles ?? []))].join('/');
101
+ const clis = [...new Set((group.members ?? []).map(m => m.cli))].join('/');
102
+ const parts = [`${oneLine(id, 64)} (members: ${(group.members ?? []).length}`];
103
103
  if (clis)
104
104
  parts.push(`clis: ${oneLine(clis, 96)}`);
105
105
  if (roles)
106
106
  parts.push(`roles: ${oneLine(roles, 96)}`);
107
107
  if (group.maxParallel !== undefined)
108
108
  parts.push(`maxParallel: ${group.maxParallel}`);
109
+ if (group.efforts)
110
+ parts.push(`efforts: ${oneLine(group.efforts.join('/'), 96)}`);
111
+ if (group.dataPolicy)
112
+ parts.push(`dataPolicy: ${oneLine(JSON.stringify(group.dataPolicy), 240)}`);
109
113
  if (group.requiredRoles?.length)
110
114
  parts.push(`requiredRoles: ${group.requiredRoles.join('/')}`);
111
115
  parts.push('configured');
@@ -147,13 +151,13 @@ async function observationSummaries(entries, deps) {
147
151
  `Cached model observations read at ${readAt.toISOString()}; catalog fetchedAt: ${stamp(catalog?.discovery?.fetchedAt)}. Ages are measured at this read and grow afterward. This cache may differ from the resolver's current installation observations; no eligibility read is performed by context generation. Missing or ambiguous observations have unknown age.`,
148
152
  '',
149
153
  ...entries.map(([id, group]) => {
150
- const observations = group.members.flatMap(member => {
154
+ const observations = (group.members ?? []).flatMap(member => {
151
155
  // Mirror the resolver's exact, unambiguous id/API-id lookup per installation.
152
156
  const rows = catalog?.discovery?.instances?.[member.cli]?.models
153
157
  .filter(row => row.id === member.model || row.apiModelId === member.model) ?? [];
154
158
  return rows.length === 1 && rows[0].worker ? [rows[0].worker] : [];
155
159
  });
156
- return `- ${oneLine(id, 64)} cached observations: ${observations.length}/${group.members.length} members; observedAt: ${range(observations.map(row => row.observedAt))}; verifiedAt: ${range(observations.map(row => row.verifiedAt))}.`;
160
+ return `- ${oneLine(id, 64)} cached observations: ${observations.length}/${(group.members ?? []).length} members; observedAt: ${range(observations.map(row => row.observedAt))}; verifiedAt: ${range(observations.map(row => row.verifiedAt))}.`;
157
161
  }),
158
162
  '',
159
163
  ];
@@ -313,7 +317,7 @@ export async function buildParallelismSection(projectPath, deps = {}) {
313
317
  const hard = healthValue(health, 'hardTimeoutSeconds', 1800);
314
318
  lines.push('### Orchestration groups (group dispatch enabled)');
315
319
  lines.push('');
316
- const entries = Object.entries(cfg.parallelism?.groups ?? {});
320
+ const entries = Object.entries(resolved.groups ?? {});
317
321
  if (entries.length > 0) {
318
322
  // Slice before summarizing so at most MAX_SHOWN_GROUPS summaries are built.
319
323
  const shown = groupSummaries(entries.slice(0, MAX_SHOWN_GROUPS), cfg.parallelism).join(' | ');
@@ -328,6 +332,14 @@ export async function buildParallelismSection(projectPath, deps = {}) {
328
332
  lines.push('');
329
333
  lines.push(...await observationSummaries(entries.slice(0, MAX_SHOWN_GROUPS), deps));
330
334
  const models = await new ConfigLoader(projectPath, { aiwgHome: resolveAiwgHome() }).load(cfg.parallelism);
335
+ for (const [pointer, origins] of Object.entries(models.workerPolicyOrigins ?? {})) {
336
+ if (!pointer.startsWith('/parallelism/groups/') || !pointer.endsWith('/template'))
337
+ continue;
338
+ lines.push(`Template expansion ${oneLine(pointer, 160)}: ${origins.map(origin => `${oneLine(origin.source, 240)}#${oneLine(origin.pointer, 160)} (${origin.digest})`).join(' → ')}. Contributions are retained in workerPolicyOrigins; inherited ceilings remain binding.`);
339
+ }
340
+ const aliases = Object.entries(models.defaults.workerSelection?.roleGuidance ?? {}).filter(([, guidance]) => guidance.baseRole);
341
+ for (const [role, guidance] of aliases)
342
+ lines.push(`Project role ${role} aliases ${guidance.baseRole}; ordered groups: ${guidance.groups.join(', ')}. Resolver eligibility and base-role qualification remain required.`);
331
343
  const freshness = models.defaults.workerSelection.freshness;
332
344
  lines.push(`Resolver freshness limits from merged model policy: observation warning after ${freshness.warnHours} hours; observation blocked after ${freshness.blockHours} hours; verification blocked after ${freshness.policyDays} days. Future-dated evidence is blocked. Re-resolve before launch; cached ages do not establish eligibility.`);
333
345
  lines.push('');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.10.9",
3
+ "version": "2026.10.11",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,7 +49,17 @@
49
49
  "tools/ralph-external/session-launcher.mjs",
50
50
  "tools/ralph-external/process-monitor.mjs",
51
51
  "tools/ralph-external/lib/session-environment.mjs",
52
+ "tools/ralph-external/lib/worker-inputs.mjs",
53
+ "tools/ralph-external/lib/worker-final.mjs",
54
+ "tools/ralph-external/lib/worker-launch-cli.mjs",
52
55
  "tools/ralph-external/lib/worker-isolation.mjs",
56
+ "tools/ralph-external/lib/worker-native-sandbox.mjs",
57
+ "tools/ralph-external/lib/worker-isolation-status.mjs",
58
+ "tools/ralph-external/lib/worker-retry-evidence.mjs",
59
+ "tools/ralph-external/lib/worker-termination.mjs",
60
+ "tools/ralph-external/lib/worker-invocation-processes.mjs",
61
+ "tools/ralph-external/lib/worker-invocation-ancestry.mjs",
62
+ "tools/ralph-external/lib/worker-context.mjs",
53
63
  "tools/ralph-external/lib/systemd-client.mjs",
54
64
  "tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs",
55
65
  "tools/ralph-external/lib/launcher-recovery-evidence.mjs",
@@ -57,7 +67,11 @@
57
67
  "tools/ralph-external/lib/worker-overflow.mjs",
58
68
  "tools/scripts/resource-preflight.mjs",
59
69
  "tools/ralph-external/lib/provider-adapter.mjs",
70
+ "tools/ralph-external/lib/strict-worker-json.mjs",
71
+ "tools/ralph-external/lib/session-attempt.mjs",
72
+ "tools/ralph-external/lib/managed-worker-attempt.mjs",
60
73
  "tools/ralph-external/lib/worker-health.mjs",
74
+ "tools/ralph-external/lib/worker-artifacts.mjs",
61
75
  "tools/ralph-external/lib/claude-adapter.mjs",
62
76
  "tools/ralph-external/lib/codex-adapter.mjs",
63
77
  "tools/ralph-external/lib/opencode-adapter.mjs",
@@ -1643,6 +1643,7 @@
1643
1643
  "type": "object",
1644
1644
  "additionalProperties": false,
1645
1645
  "properties": {
1646
+ "invocationId": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{0,51}(?![\\s\\S])" },
1646
1647
  "isolation": {
1647
1648
  "enum": ["systemd-unit", "native-sandbox"]
1648
1649
  },
@@ -1703,28 +1704,24 @@
1703
1704
  ]
1704
1705
  },
1705
1706
  "artifacts": {
1706
- "type": "array",
1707
- "maxItems": 16,
1707
+ "type": "array", "maxItems": 16,
1708
+ "contains": { "type": "object", "required": ["location"], "properties": { "location": { "const": "workspace-relative" } } },
1709
+ "minContains": 0, "maxContains": 1,
1708
1710
  "items": {
1709
- "type": "object",
1710
- "additionalProperties": false,
1711
+ "type": "object", "additionalProperties": false,
1711
1712
  "properties": {
1712
- "name": {
1713
- "type": "string",
1714
- "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}(?![\\s\\S])"
1715
- },
1716
- "content": {
1717
- "type": "string",
1718
- "maxLength": 1048576
1719
- },
1720
- "location": {
1721
- "const": "run-directory"
1722
- }
1713
+ "name": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,63}(?![\\s\\S])" },
1714
+ "content": { "type": "string", "maxLength": 1048576 },
1715
+ "location": { "enum": ["run-directory", "workspace-relative"] },
1716
+ "prefix": { "type": "string", "minLength": 1, "maxLength": 256 },
1717
+ "relativePath": { "type": "string", "minLength": 1, "maxLength": 4096 }
1723
1718
  },
1724
- "required": [
1725
- "name",
1726
- "content"
1727
- ]
1719
+ "required": ["name", "content"],
1720
+ "allOf": [{
1721
+ "if": { "required": ["location"], "properties": { "location": { "const": "workspace-relative" } } },
1722
+ "then": { "required": ["prefix", "relativePath"] },
1723
+ "else": { "not": { "anyOf": [{ "required": ["prefix"] }, { "required": ["relativePath"] }] } }
1724
+ }]
1728
1725
  }
1729
1726
  },
1730
1727
  "briefPath": {
@@ -1871,6 +1868,10 @@
1871
1868
  "controlEvidence"
1872
1869
  ],
1873
1870
  "allOf": [
1871
+ {
1872
+ "if": { "required": ["artifacts"], "properties": { "artifacts": { "type": "array", "contains": { "type": "object", "required": ["location"], "properties": { "location": { "const": "workspace-relative" } } } } } },
1873
+ "then": { "required": ["invocationId"], "properties": { "transport": { "const": "shell" } } }
1874
+ },
1874
1875
  {
1875
1876
  "if": {
1876
1877
  "properties": {
@@ -184,6 +184,13 @@
184
184
  "type": "object",
185
185
  "additionalProperties": false,
186
186
  "properties": {
187
+ "baseRole": {
188
+ "enum": [
189
+ "efficiency",
190
+ "coding",
191
+ "reasoning"
192
+ ]
193
+ },
187
194
  "groups": {
188
195
  "type": "array",
189
196
  "items": {
@@ -248,6 +255,15 @@
248
255
  "type": "object",
249
256
  "additionalProperties": false,
250
257
  "properties": {
258
+ "localOnlyPaths": {
259
+ "type": "array",
260
+ "uniqueItems": true,
261
+ "items": {
262
+ "type": "string",
263
+ "pattern": "^(?!.*(?:^|/)\\.{1,2}(?:/|$))[A-Za-z0-9_.@+,-]+(?:/[A-Za-z0-9_.@+,-]+)*$(?![\\s\\S])"
264
+ },
265
+ "description": "Literal project-relative local-only files or directory subtrees; deployed conductor guards have limited shell coverage."
266
+ },
251
267
  "allowedProcessing": {
252
268
  "type": "array",
253
269
  "items": {
@@ -0,0 +1,35 @@
1
+ // Apply project settings and context after provider file deployment.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { initializeFrameworkWorkspace, ensureDir } from './base.mjs';
5
+ import { deployHookFile } from './claude.mjs';
6
+ import { deployConductorDataGuard } from './conductor-data-guard.mjs';
7
+
8
+ export async function postDeploy(targetDir, opts) {
9
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
10
+ await deployConductorDataGuard(targetDir, 'claude', opts);
11
+
12
+ // Claude-specific post-deployment (settings.json, etc.)
13
+ const claudeDir = path.join(targetDir, '.claude');
14
+ const settingsPath = path.join(claudeDir, 'settings.json');
15
+
16
+ // If settings.json doesn't exist, create a minimal one
17
+ if (!fs.existsSync(settingsPath) && !opts.dryRun) {
18
+ ensureDir(claudeDir);
19
+ const settings = {
20
+ version: '1.0',
21
+ created: new Date().toISOString(),
22
+ aiwg: {
23
+ enabled: true,
24
+ mode: opts.mode || 'all'
25
+ }
26
+ };
27
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
28
+ console.log('Created .claude/settings.json');
29
+ }
30
+
31
+ // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
32
+ if (opts.srcRoot && opts.contextFiles !== false) {
33
+ deployHookFile(targetDir, opts);
34
+ }
35
+ }
@@ -1,3 +1,5 @@
1
+ import { postDeploy } from './claude-post-deploy.mjs';
2
+ export { postDeploy };
1
3
  import { importImpl } from '../../_resolve-impl.mjs';
2
4
  /**
3
5
  * Claude Code Provider
@@ -43,7 +45,6 @@ import {
43
45
  pruneStaleAiwgSkills,
44
46
  computeAllKernelNames,
45
47
  parseFrontmatter,
46
- initializeFrameworkWorkspace,
47
48
  filterAgentFiles,
48
49
  getAddonAgentFiles,
49
50
  getAddonCommandFiles,
@@ -395,34 +396,6 @@ export function createAgentsMd(target, srcRoot, dryRun) {
395
396
  // Post-Deployment
396
397
  // ============================================================================
397
398
 
398
- export async function postDeploy(targetDir, opts) {
399
- // Initialize framework workspace structure
400
- initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
401
-
402
- // Claude-specific post-deployment (settings.json, etc.)
403
- const claudeDir = path.join(targetDir, '.claude');
404
- const settingsPath = path.join(claudeDir, 'settings.json');
405
-
406
- // If settings.json doesn't exist, create a minimal one
407
- if (!fs.existsSync(settingsPath) && !opts.dryRun) {
408
- ensureDir(claudeDir);
409
- const settings = {
410
- version: '1.0',
411
- created: new Date().toISOString(),
412
- aiwg: {
413
- enabled: true,
414
- mode: opts.mode || 'all'
415
- }
416
- };
417
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
418
- console.log('Created .claude/settings.json');
419
- }
420
-
421
- // Hook file architecture: write AIWG.md and wire @AIWG.md into CLAUDE.md
422
- if (opts.srcRoot && opts.contextFiles !== false) {
423
- deployHookFile(targetDir, opts);
424
- }
425
- }
426
399
 
427
400
  /**
428
401
  * Deploy the AIWG.md hook file and add @AIWG.md directive to CLAUDE.md.
@@ -444,7 +417,7 @@ function interpolateHookTokens(content, counts, targetDir) {
444
417
  });
445
418
  }
446
419
 
447
- function deployHookFile(targetDir, opts) {
420
+ export function deployHookFile(targetDir, opts) {
448
421
  const { srcRoot, dryRun, counts } = opts;
449
422
  const templatePath = path.join(srcRoot, 'agentic', 'code', 'frameworks', 'sdlc-complete', 'templates', 'project', 'AIWG.md');
450
423
  const hookDest = path.join(targetDir, 'AIWG.md');
@@ -0,0 +1,13 @@
1
+ // Apply project settings and context after provider file deployment.
2
+ import { initializeFrameworkWorkspace } from './base.mjs';
3
+ import { createAgentsMd } from './codex.mjs';
4
+ import { deployConductorDataGuard } from './conductor-data-guard.mjs';
5
+
6
+ export async function postDeploy(targetDir, opts) {
7
+ initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
8
+ await deployConductorDataGuard(targetDir, 'codex', opts);
9
+
10
+ if (opts.createAgentsMd) {
11
+ if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
12
+ }
13
+ }
@@ -1,3 +1,5 @@
1
+ import { postDeploy } from './codex-post-deploy.mjs';
2
+ export { postDeploy };
1
3
  import { importImpl } from '../../_resolve-impl.mjs';
2
4
  /**
3
5
  * OpenAI Codex Provider
@@ -47,7 +49,6 @@ import {
47
49
  writeFile,
48
50
  deployFiles,
49
51
  createAgentsMdFromTemplate,
50
- initializeFrameworkWorkspace,
51
52
  getAddonAgentFiles,
52
53
  getAddonCommandFiles,
53
54
  getAddonSkillDirs,
@@ -511,13 +512,6 @@ export function generatePluginBundle(targetDir, opts = {}) {
511
512
  // Post-Deployment
512
513
  // ============================================================================
513
514
 
514
- export async function postDeploy(targetDir, opts) {
515
- initializeFrameworkWorkspace(targetDir, opts.mode, opts.dryRun, opts.srcRoot);
516
-
517
- if (opts.createAgentsMd) {
518
- if (opts.contextFiles !== false) createAgentsMd(targetDir, opts.srcRoot, opts.dryRun);
519
- }
520
- }
521
515
 
522
516
  // ============================================================================
523
517
  // File Extension