wicked-crew 0.7.22 → 0.7.24

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 (36) hide show
  1. package/dist/api/audit.d.ts +4 -1
  2. package/dist/api/audit.d.ts.map +1 -1
  3. package/dist/api/audit.js +5 -1
  4. package/dist/api/audit.js.map +1 -1
  5. package/dist/api/eval-store.d.ts +85 -0
  6. package/dist/api/eval-store.d.ts.map +1 -0
  7. package/dist/api/eval-store.js +176 -0
  8. package/dist/api/eval-store.js.map +1 -0
  9. package/dist/api/governance-steering.d.ts +5 -0
  10. package/dist/api/governance-steering.d.ts.map +1 -1
  11. package/dist/api/governance-steering.js +8 -9
  12. package/dist/api/governance-steering.js.map +1 -1
  13. package/dist/api/routes.d.ts +11 -0
  14. package/dist/api/routes.d.ts.map +1 -1
  15. package/dist/api/routes.js +116 -19
  16. package/dist/api/routes.js.map +1 -1
  17. package/dist/api/run-timing-index.d.ts +54 -0
  18. package/dist/api/run-timing-index.d.ts.map +1 -0
  19. package/dist/api/run-timing-index.js +82 -0
  20. package/dist/api/run-timing-index.js.map +1 -0
  21. package/dist/api/server.d.ts +4 -0
  22. package/dist/api/server.d.ts.map +1 -1
  23. package/dist/api/server.js +15 -1
  24. package/dist/api/server.js.map +1 -1
  25. package/dist/api/testing.d.ts +17 -1
  26. package/dist/api/testing.d.ts.map +1 -1
  27. package/dist/api/testing.js +93 -22
  28. package/dist/api/testing.js.map +1 -1
  29. package/dist/studio/assets/index-BmXt63Ij.js +537 -0
  30. package/dist/studio/assets/index-eso7X_3n.css +32 -0
  31. package/dist/studio/index.html +2 -2
  32. package/dist/studio/testid-inventory.json +411 -150
  33. package/endpoint-manifest.json +20 -1
  34. package/package.json +2 -2
  35. package/dist/studio/assets/index-CDWyxcSj.js +0 -537
  36. package/dist/studio/assets/index-Dz6mXrD5.css +0 -32
@@ -36,6 +36,7 @@ import { AuditLog } from './audit.js';
36
36
  import { AcpFoldCache, EngineVersionCache, eventsDirOf, installedPackageVersion, listStoreFiles, readStudioBundleVersion, } from './diagnostics.js';
37
37
  import { RetryIndex } from './retry-index.js';
38
38
  import { GroupIndex } from './group-index.js';
39
+ import { RunTimingIndex, recordRunLaunched } from './run-timing-index.js';
39
40
  import { GuidanceIndex } from './guidance-index.js';
40
41
  import { DeliveryIndex, gitRunBranchIsEmpty, gitWorktreeIsClean, isDeliverConflictStranded, prUrlFrom, } from './delivery-index.js';
41
42
  import { DeliveryDerivationCache } from './delivery-cache.js';
@@ -93,6 +94,39 @@ function invalidBody(err, what) {
93
94
  function firstQueryValue(v) {
94
95
  return Array.isArray(v) ? v[0] : v;
95
96
  }
97
+ /**
98
+ * Parse an optional `since`/`until` date-bound query param (unix SECONDS). Returns the parsed
99
+ * non-negative integer, `undefined` when the param is absent or blank, or the sentinel `'invalid'`
100
+ * when it is present but not a non-negative integer — the caller turns `'invalid'` into a 400 that
101
+ * names the offending param (a silently-dropped bad bound would answer a different date question
102
+ * than the one asked).
103
+ */
104
+ function parseUnixSecondsParam(raw) {
105
+ if (raw === undefined || raw.trim() === '')
106
+ return undefined;
107
+ const n = Number(raw);
108
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0)
109
+ return 'invalid';
110
+ return n;
111
+ }
112
+ /**
113
+ * Inclusive `[since, until]` date-range predicate (unix seconds) shared by the memory + rules
114
+ * browse filters. An item with NO `created_at` is EXCLUDED whenever either bound is set — an
115
+ * unknown creation time is never asserted in range, and never fabricated (crew never invents a
116
+ * timestamp for a row the store did not stamp). With NEITHER bound set every item passes, so the
117
+ * un-filtered browse is unchanged.
118
+ */
119
+ function withinDateRange(createdAt, since, until) {
120
+ if (since === undefined && until === undefined)
121
+ return true;
122
+ if (typeof createdAt !== 'number')
123
+ return false;
124
+ if (since !== undefined && createdAt < since)
125
+ return false;
126
+ if (until !== undefined && createdAt > until)
127
+ return false;
128
+ return true;
129
+ }
96
130
  /** A JSON object whose every value is a string (a facet/intent tuple, axis→value). */
97
131
  function isStringRecord(v) {
98
132
  return (typeof v === 'object' &&
@@ -201,6 +235,10 @@ function shapeMemoryItem(raw) {
201
235
  };
202
236
  if (typeof r['score'] === 'number')
203
237
  item.score = r['score'];
238
+ // `created_at` (unix seconds) rides through from estate `memory.list` verbatim — the "added" time
239
+ // the surface date-filters on. Absent on an item the store did not stamp; never fabricated.
240
+ if (typeof r['created_at'] === 'number')
241
+ item.created_at = r['created_at'];
204
242
  return item;
205
243
  }
206
244
  /** Shape the estate `memory.coverage` result (`{ total, by_tier, by_kind }`) into the wire
@@ -392,6 +430,7 @@ runtime = {}) {
392
430
  const signedIn = runtime.signedIn ?? signedInHeuristic;
393
431
  const retryIndex = runtime.retryIndex ?? new RetryIndex();
394
432
  const groupIndex = runtime.groupIndex ?? new GroupIndex();
433
+ const runTimingIndex = runtime.runTimingIndex ?? new RunTimingIndex();
395
434
  const guidanceIndex = runtime.guidanceIndex ?? new GuidanceIndex();
396
435
  const deliveryIndex = runtime.deliveryIndex ?? new DeliveryIndex();
397
436
  const worktreeExists = runtime.worktreeExists ?? ((p) => existsSync(p));
@@ -491,6 +530,13 @@ runtime = {}) {
491
530
  const guidance = guidanceIndex.guidanceFor(view.session.id);
492
531
  if (guidance !== undefined)
493
532
  view.session.guidance = guidance;
533
+ // Run launch time (home command-center run metrics): unix SECONDS from the `run.launched`
534
+ // audit entry, ABSENT when the daemon has no launch record for this run (onboarding/campaign
535
+ // runs launched off POST /runs, pre-field runs) — never fabricated, so a bucketed KPI can
536
+ // exclude an undated run rather than dating it with a false now.
537
+ const createdAt = runTimingIndex.createdAtFor(view.session.id);
538
+ if (createdAt !== undefined)
539
+ view.session.created_at = createdAt;
494
540
  const state = resolveDelivery(view, conflictStrand);
495
541
  view.session.delivery = state.delivery;
496
542
  if (state.deliverUrl !== undefined)
@@ -966,24 +1012,29 @@ runtime = {}) {
966
1012
  // Who launched it — the engine's LaunchOptions carries no actor field
967
1013
  // (checked, wicked-core-ts 0.6.0), so the crew-side trail is the system
968
1014
  // of record for run provenance (task #88).
969
- audit.record('run.launched', actorOf(req), {
970
- runId,
971
- detail: {
972
- ...(b.workflow !== undefined ? { workflow: b.workflow } : {}),
973
- ...(b.repoRef !== undefined ? { repoRef: b.repoRef } : {}),
974
- ...(b.projectId !== undefined ? { projectId: b.projectId } : {}),
975
- // crew#393: the RESOLVED delivery decision, not just the caller's field — so the
976
- // trail says what the run will actually do, and whether the daemon decided it.
977
- deliver,
978
- ...(deliverDefaulted ? { deliverDefaulted: true } : {}),
979
- // CREW-UX-3: the trail is the durable record of lineage — the retry index (and a
980
- // restarted daemon's hydrate) reads it back from exactly this entry.
981
- ...(b.retryOf !== undefined ? { retryOf: b.retryOf } : {}),
982
- // wicked-studio#27: the trail is likewise the durable record of the group attach —
983
- // the group index (and a restarted daemon's hydrate) reads it back from here.
984
- ...(b.campaignId !== undefined ? { campaignId: b.campaignId } : {}),
985
- ...(b.groupLabel !== undefined ? { groupLabel: b.groupLabel } : {}),
986
- },
1015
+ // `run.launched` is the system of record for run provenance (the engine's LaunchOptions
1016
+ // carries no actor field — task #88). `recordRunLaunched` writes the trail entry AND stamps
1017
+ // the run-timing index with the SAME durable `ts` (home command-center run metrics): the live
1018
+ // `created_at` equals what a restart rehydrates from the trail — no 1s drift, and no fabricated
1019
+ // value (audit disabled → nothing stamped → `created_at` ABSENT, the honest answer for a run
1020
+ // with no durable launch record). `createServer` always builds a REAL trail, so this fires on
1021
+ // every production launch. The SHARED helper (Copilot #466) is why the recon/steering-author
1022
+ // launchers stamp identically instead of leaving their runs undated until a restart.
1023
+ recordRunLaunched(audit, runTimingIndex, actorOf(req), runId, {
1024
+ ...(b.workflow !== undefined ? { workflow: b.workflow } : {}),
1025
+ ...(b.repoRef !== undefined ? { repoRef: b.repoRef } : {}),
1026
+ ...(b.projectId !== undefined ? { projectId: b.projectId } : {}),
1027
+ // crew#393: the RESOLVED delivery decision, not just the caller's field — so the
1028
+ // trail says what the run will actually do, and whether the daemon decided it.
1029
+ deliver,
1030
+ ...(deliverDefaulted ? { deliverDefaulted: true } : {}),
1031
+ // CREW-UX-3: the trail is the durable record of lineage — the retry index (and a
1032
+ // restarted daemon's hydrate) reads it back from exactly this entry.
1033
+ ...(b.retryOf !== undefined ? { retryOf: b.retryOf } : {}),
1034
+ // wicked-studio#27: the trail is likewise the durable record of the group attach —
1035
+ // the group index (and a restarted daemon's hydrate) reads it back from here.
1036
+ ...(b.campaignId !== undefined ? { campaignId: b.campaignId } : {}),
1037
+ ...(b.groupLabel !== undefined ? { groupLabel: b.groupLabel } : {}),
987
1038
  });
988
1039
  if (b.retryOf !== undefined)
989
1040
  retryIndex.set(runId, b.retryOf);
@@ -2063,6 +2114,27 @@ runtime = {}) {
2063
2114
  return invalid('status', status, 'active|retired|all');
2064
2115
  }
2065
2116
  const layer = facet('layer');
2117
+ // `since`/`until` (unix SECONDS, inclusive) date-bound the listed rows by each rule's
2118
+ // `created_at`. A present-but-non-integer bound is a 400. NOTE: `created_at` is wired-but-
2119
+ // pending a core-ts bump — it landed on the engine's `ConformanceRule` after the published
2120
+ // core-ts this daemon builds against, so on the current binding NO rule carries it and this
2121
+ // filter narrows to empty. The pass-through is verbatim (`listConformanceRules` returns the
2122
+ // parsed rows unchanged), so the bound starts working the moment crew re-pins to a core-ts
2123
+ // that surfaces it — a rule with no `created_at` is excluded, never dated with a fabricated now.
2124
+ const since = facet('since');
2125
+ const sinceSecs = parseUnixSecondsParam(since);
2126
+ if (sinceSecs === 'invalid') {
2127
+ return reply
2128
+ .code(400)
2129
+ .send({ error: `since must be a non-negative integer in unix seconds (got \`${since ?? ''}\`)` });
2130
+ }
2131
+ const until = facet('until');
2132
+ const untilSecs = parseUnixSecondsParam(until);
2133
+ if (untilSecs === 'invalid') {
2134
+ return reply
2135
+ .code(400)
2136
+ .send({ error: `until must be a non-negative integer in unix seconds (got \`${until ?? ''}\`)` });
2137
+ }
2066
2138
  const rules = (await adapter.listConformanceRules()).filter((r) => (severity === undefined || r.severity === severity) &&
2067
2139
  (ruleType === undefined || r.rule_type === ruleType) &&
2068
2140
  (layer === undefined || r.targets.layer === layer) &&
@@ -2070,7 +2142,9 @@ runtime = {}) {
2070
2142
  // steering engine always stamps it, but filter defensively over mixed-era rows.
2071
2143
  (steeringType === undefined || (r.steering_type ?? DEFAULT_STEERING_TYPE) === steeringType) &&
2072
2144
  // `retired` is absent on rows written before the field existed, which read as active.
2073
- (status === 'all' || (status === 'retired') === (r.retired === true)));
2145
+ (status === 'all' || (status === 'retired') === (r.retired === true)) &&
2146
+ // Undated rules (every rule on a pre-bump core-ts) are excluded once a bound is set.
2147
+ withinDateRange(r.created_at, sinceSecs, untilSecs));
2074
2148
  return { rules };
2075
2149
  });
2076
2150
  app.get(`${V}/governance/claims`, async () => {
@@ -2991,6 +3065,18 @@ runtime = {}) {
2991
3065
  }
2992
3066
  limit = n;
2993
3067
  }
3068
+ // `since`/`until` (unix SECONDS, inclusive) date-bound the complete set crew-side by each
3069
+ // memory's `created_at`. A present-but-non-integer bound is a 400 (fail-loud, never a silent
3070
+ // no-filter); a memory with no `created_at` is excluded once either bound is set (see
3071
+ // withinDateRange). Applied BEFORE the row cap, so `limit` cuts the dated set.
3072
+ const since = parseUnixSecondsParam(firstQueryValue(q.since));
3073
+ if (since === 'invalid') {
3074
+ return reply.code(400).send({ error: '`since` must be a non-negative integer (unix seconds)' });
3075
+ }
3076
+ const until = parseUnixSecondsParam(firstQueryValue(q.until));
3077
+ if (until === 'invalid') {
3078
+ return reply.code(400).send({ error: '`until` must be a non-negative integer (unix seconds)' });
3079
+ }
2994
3080
  // `query`, when present, is a case-insensitive CONTENT substring filter over the complete set
2995
3081
  // — a management search that finds faceted memories too (unlike relevance recall).
2996
3082
  const query = (firstQueryValue(q.query) ?? '').trim().toLowerCase();
@@ -3007,6 +3093,9 @@ runtime = {}) {
3007
3093
  const pairs = Object.entries(facetFilter);
3008
3094
  memories = memories.filter((m) => pairs.every(([k, v]) => m.facets[k] === v));
3009
3095
  }
3096
+ if (since !== undefined || until !== undefined) {
3097
+ memories = memories.filter((m) => withinDateRange(m.created_at, since, until));
3098
+ }
3010
3099
  if (limit !== undefined)
3011
3100
  memories = memories.slice(0, limit);
3012
3101
  const body = { memories };
@@ -3094,6 +3183,8 @@ runtime = {}) {
3094
3183
  audit,
3095
3184
  actorOf,
3096
3185
  roster: () => CoreAdapter.roster(),
3186
+ // So the steering-author run's `run.launched` entry stamps `created_at` live too (Copilot #466).
3187
+ runTimingIndex,
3097
3188
  });
3098
3189
  // ── Testing (crew-testing) — governance evals + eval corpora + the recon trigger ────────────
3099
3190
  // Run the evals (does the steering corpus catch what it claims to?) and import a named eval
@@ -3105,6 +3196,12 @@ runtime = {}) {
3105
3196
  actorOf,
3106
3197
  roster: () => CoreAdapter.roster(),
3107
3198
  projects: { bus: projects.bus, index: projects.index },
3199
+ // So the recon fan's `run.launched` entries stamp `created_at` live too (Copilot #466), not
3200
+ // just after a restart re-hydrates the trail.
3201
+ runTimingIndex,
3202
+ // The eval history store: `createServer` supplies a real one (state-home-rooted); a
3203
+ // directly-driven route set records nothing unless it injects one (no `~/.wicked-crew` writes).
3204
+ ...(runtime.evalStore !== undefined ? { evalStore: runtime.evalStore } : {}),
3108
3205
  });
3109
3206
  // ── The wicked-interactive bridge, reverse-proxied (DES-MERGE-001 §5.3/§7.2) ──
3110
3207
  // Mounted BESIDE the routes above and under the same `${V}` prefix, so it inherits one