@aiwg/cli 2026.10.10 → 2026.10.12
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/agentic/code/providers/model-capabilities.v1.json +78 -14
- package/agentic/code/providers/model-catalog.v1.json +62 -12
- package/dist/src/artifacts/binding-target-identity.js +51 -0
- package/dist/src/artifacts/checksum-manifest.js +12 -0
- package/dist/src/artifacts/index-builder.js +11 -13
- package/dist/src/artifacts/query-engine.js +5 -11
- package/dist/src/cli/handlers/doctor-help.js +32 -0
- package/dist/src/cli/handlers/gates.js +19 -18
- package/dist/src/cli/handlers/models-help.js +7 -1
- package/dist/src/cli/handlers/models.js +3 -3
- package/dist/src/cli/handlers/script-runner.js +8 -5
- package/dist/src/cli/handlers/sessions-intake.js +104 -0
- package/dist/src/cli/handlers/sessions-tail.js +172 -0
- package/dist/src/cli/handlers/sessions.js +287 -31
- package/dist/src/cli/handlers/skill-lint.js +10 -3
- package/dist/src/cli/handlers/steward-format.js +100 -0
- package/dist/src/cli/handlers/steward-models.js +167 -0
- package/dist/src/cli/handlers/steward.js +14 -248
- package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
- package/dist/src/cli/handlers/use.js +18 -28
- package/dist/src/cli/handlers/utilities.js +1 -24
- package/dist/src/config/aiwg-config.js +7 -0
- package/dist/src/config/authorization-cli.js +89 -0
- package/dist/src/config/cli-help.js +1 -0
- package/dist/src/config/cli.js +10 -1
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/decision/context-pruning.js +3 -2
- package/dist/src/decision/ensemble/contract.js +15 -4
- package/dist/src/decision/ensemble-study/artifacts.js +5 -0
- package/dist/src/decision/ensemble-study/score.js +4 -3
- package/dist/src/decision/feature-export.js +3 -9
- package/dist/src/decision/heldout/collector.js +2 -2
- package/dist/src/decision/issue-triage/runtime.js +3 -9
- package/dist/src/decision/qualification/gates.js +2 -1
- package/dist/src/decision/qualification/release.js +2 -3
- package/dist/src/decision/routing/report.js +2 -0
- package/dist/src/decision/sdlc-screening.js +3 -5
- package/dist/src/decision/sensitivity/comparative.js +5 -3
- package/dist/src/gates/driver.js +6 -77
- package/dist/src/gates/evaluate.js +4 -11
- package/dist/src/gates/floors.js +4 -4
- package/dist/src/gates/index.js +3 -3
- package/dist/src/gates/lattice.js +26 -0
- package/dist/src/gates/registry.js +2 -1
- package/dist/src/gates/report.js +19 -7
- package/dist/src/issues/audit-cli.js +120 -0
- package/dist/src/issues/audit-contract.js +64 -0
- package/dist/src/issues/audit-local.js +82 -0
- package/dist/src/issues/audit.js +220 -0
- package/dist/src/lint/rule-activation.js +15 -1
- package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
- package/dist/src/mission-protocol/canonical-codecs.js +270 -0
- package/dist/src/mission-protocol/codecs.js +22 -550
- package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
- package/dist/src/mission-protocol/health-trace-types.js +43 -0
- package/dist/src/mission-protocol/types.js +2 -42
- package/dist/src/mission-protocol/worker-contract-json.js +10 -0
- package/dist/src/mission-protocol/worker-review-gate.js +100 -0
- package/dist/src/mission-protocol/worker-review-types.js +2 -0
- package/dist/src/models/catalog-policy.js +280 -0
- package/dist/src/models/index.js +1 -1
- package/dist/src/models/model-capabilities.v1.json +78 -14
- package/dist/src/models/model-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -539
- package/dist/src/models/resolver.js +26 -8
- package/dist/src/models/router.js +15 -2
- package/dist/src/models/tiers.js +13 -0
- package/dist/src/models/types.js +0 -6
- package/dist/src/models/worker-ceilings.js +59 -0
- package/dist/src/models/worker-invocation-policy.js +243 -0
- package/dist/src/models/worker-plan.js +16 -20
- package/dist/src/models/worker-review-lineage.js +55 -0
- package/dist/src/models/worker-selection-policy.js +1 -1
- package/dist/src/models/wrapper-route.js +6 -3
- package/dist/src/policy/authorization.js +30 -1
- package/dist/src/policy/hitl-config.js +63 -0
- package/dist/src/policy/hitl-receipt.js +57 -0
- package/dist/src/policy/hitl-types.js +4 -0
- package/dist/src/policy/hitl-validation.js +39 -0
- package/dist/src/policy/hitl.js +117 -0
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/rules/activation.d.mts +1 -1
- package/dist/src/rules/activation.mjs +0 -2
- package/dist/src/rules/gate-coverage.js +51 -0
- package/dist/src/rules/gate-references.js +16 -0
- package/dist/src/rules/rule-frontmatter.schema.json +7 -1
- package/dist/src/serve/worker-command-request.js +21 -4
- package/dist/src/serve/worker-invocation-preparation.js +3 -1
- package/dist/src/serve/worker-invocation.js +21 -10
- package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
- package/dist/src/sessions/adapters/grok-build-native.js +269 -0
- package/dist/src/sessions/adapters/grok-build.js +57 -15
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
- package/dist/src/sessions/adapters/hermes.js +42 -3
- package/dist/src/sessions/adapters/warp.js +1 -1
- package/dist/src/sessions/batch-import.js +44 -6
- package/dist/src/sessions/contracts.js +20 -1
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +9 -232
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +88 -9
- package/dist/src/skills/agent-skill-sidecar.js +28 -0
- package/dist/src/skills/agent-skills.js +4 -23
- package/dist/src/skills/claude-surface-projection.js +25 -0
- package/dist/src/skills/deployer.js +8 -288
- package/dist/src/skills/deployment-policy.js +114 -0
- package/dist/src/skills/deployment-projection.js +152 -0
- package/dist/src/skills/deployment-types.js +20 -0
- package/dist/src/skills/exporter.js +8 -31
- package/dist/src/skills/provider-extensions.js +41 -0
- package/dist/src/skills/run.js +6 -0
- package/dist/src/skills/types.js +0 -8
- package/dist/src/skills/validator-resources.js +68 -0
- package/dist/src/skills/validator.js +6 -55
- package/dist/src/storage/cli-migrate-options.js +125 -0
- package/dist/src/storage/cli-migrate.js +111 -0
- package/dist/src/storage/cli.js +4 -279
- package/dist/src/storage/migration-adapter.js +79 -0
- package/dist/src/storage/migration-copy.js +108 -0
- package/dist/src/storage/migration-protocol.js +14 -197
- package/dist/src/storage/migration-records.js +186 -0
- package/dist/src/storage/migration-types.js +12 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/tracker/issue-pages.js +110 -0
- package/package.json +7 -1
- package/schemas/mission-v1.schema.json +335 -0
- package/schemas/models/model-catalog.v1.schema.json +19 -1
- package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
- package/schemas/models/model-policy.v1.schema.json +1 -1
- package/tools/agents/providers/provider-paths-staging.mjs +35 -0
- package/tools/agents/providers/rule-binding-targets.mjs +146 -0
- package/tools/agents/providers/rule-delivery.mjs +7 -126
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
- package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +51 -96
- package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
1
|
+
import { existsSync, mkdirSync, realpathSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { requireFeaturePackage } from '../features/runtime.js';
|
|
4
4
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
@@ -6,6 +6,7 @@ import { createSessionAdapterRegistry } from './adapter-registry.js';
|
|
|
6
6
|
import { resolveImportExclusions } from './import-exclusions.js';
|
|
7
7
|
import { resolveImportLimits } from './import-limits.js';
|
|
8
8
|
import { acquireImportLease } from './import-lease.js';
|
|
9
|
+
import { defaultHostCatalogPath, manifestNeedsHostCatalog } from './host-catalog.js';
|
|
9
10
|
import { SessionRepository } from './repository.js';
|
|
10
11
|
import { importDiscoveryManifest } from './batch-import.js';
|
|
11
12
|
import { defaultDiscoveryManifestPath, discoverWorkspaceHistories, readDiscoveryManifest, writeDiscoveryManifest, } from './workspace-discovery.js';
|
|
@@ -69,8 +70,26 @@ export async function refreshSessionCatalog(workspace, full, waitMs, signal, wor
|
|
|
69
70
|
return 'no authorized provider roots';
|
|
70
71
|
}
|
|
71
72
|
const lease = await acquireImportLease(database, `index-refresh-${process.pid}`, { waitMs });
|
|
73
|
+
let hostLease;
|
|
72
74
|
try {
|
|
73
75
|
const repository = new SessionRepository(database);
|
|
76
|
+
// Account-scoped providers import once into the host catalog; the
|
|
77
|
+
// hourly host refresh (#310) routes them there instead of copying
|
|
78
|
+
// them into every workspace catalog (#3232). The host catalog is only
|
|
79
|
+
// opened when a manifest actually holds an account-scoped source.
|
|
80
|
+
const hostCatalogPath = defaultHostCatalogPath();
|
|
81
|
+
const needsHost = manifests.some((entry) => manifestNeedsHostCatalog(entry.manifest.sources));
|
|
82
|
+
let hostRepository;
|
|
83
|
+
if (needsHost) {
|
|
84
|
+
mkdirSync(path.dirname(hostCatalogPath), { recursive: true, mode: 0o700 });
|
|
85
|
+
// The workspace lease above does not cover the shared host catalog,
|
|
86
|
+
// so overlapping refreshes from two workspaces would stage the same
|
|
87
|
+
// replicas concurrently (#3232). Serialize host writers with the
|
|
88
|
+
// same lock pattern; the shared checkpoint then turns the second
|
|
89
|
+
// writer into duplicate references instead of a double import.
|
|
90
|
+
hostLease = await acquireImportLease(hostCatalogPath, `host-refresh-${process.pid}`, { waitMs });
|
|
91
|
+
hostRepository = new SessionRepository(hostCatalogPath);
|
|
92
|
+
}
|
|
74
93
|
try {
|
|
75
94
|
if (full)
|
|
76
95
|
repository.reindex();
|
|
@@ -82,7 +101,9 @@ export async function refreshSessionCatalog(workspace, full, waitMs, signal, wor
|
|
|
82
101
|
continue;
|
|
83
102
|
// Persist the exact discovered batch before its confirmed import.
|
|
84
103
|
await writeDiscoveryManifest(entry.path, entry.manifest);
|
|
85
|
-
const receipt = await importDiscoveryManifest({
|
|
104
|
+
const receipt = await importDiscoveryManifest({
|
|
105
|
+
manifest: entry.manifest, repository, hostRepository, hostCatalogPath, signal, limits, exclude,
|
|
106
|
+
});
|
|
86
107
|
if (receipt.totals.pending) {
|
|
87
108
|
throw new Error(`session import incomplete: ${receipt.totals.rejected} rejected, ${receipt.totals.pending} pending`);
|
|
88
109
|
}
|
|
@@ -94,10 +115,12 @@ export async function refreshSessionCatalog(workspace, full, waitMs, signal, wor
|
|
|
94
115
|
}
|
|
95
116
|
}
|
|
96
117
|
finally {
|
|
118
|
+
hostRepository?.close();
|
|
97
119
|
repository.close();
|
|
98
120
|
}
|
|
99
121
|
}
|
|
100
122
|
finally {
|
|
123
|
+
await hostLease?.release();
|
|
101
124
|
await lease.release();
|
|
102
125
|
}
|
|
103
126
|
return undefined;
|
|
@@ -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
|
-
|
|
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) {
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Follow a native source through the importer's normalization and checkpoint rules.
|
|
2
|
+
import { setTimeout } from 'node:timers/promises';
|
|
3
|
+
import { SessionContractError, assertSessionResourceLimit, assertSupportedSchemaMajor, sha256 } from './contracts.js';
|
|
4
|
+
import { IncrementalSessionImporter } from './importer.js';
|
|
5
|
+
import { normalizeSessionBatch } from './normalization.js';
|
|
6
|
+
import { assertImportByteLimit, DEFAULT_IMPORT_BYTE_LIMITS } from './import-limits.js';
|
|
7
|
+
export function assertTailSupported(adapter) {
|
|
8
|
+
if (typeof adapter.stream !== 'function' || !adapter.supportedOperations.includes('stream')) {
|
|
9
|
+
throw new SessionContractError('UNSUPPORTED_PROVIDER', `sessions tail is unsupported for provider ${adapter.provider}: adapter has no stream operation`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function tailSession(options) {
|
|
13
|
+
assertTailSupported(options.adapter);
|
|
14
|
+
assertSupportedSchemaMajor(options.source.sourceSchemaVersion, options.adapter.sourceSchemaMajor ?? 1);
|
|
15
|
+
if (options.workspaceId !== options.selectedSource.authorizedScope.workspaceId) {
|
|
16
|
+
throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'tail workspace is outside the authorized source scope');
|
|
17
|
+
}
|
|
18
|
+
const pollMs = options.pollMs ?? 500;
|
|
19
|
+
if (!Number.isInteger(pollMs) || pollMs < 1 || pollMs > 500) {
|
|
20
|
+
throw new SessionContractError('INVALID_ARGUMENT', 'tail poll interval must be between 1 and 500 ms');
|
|
21
|
+
}
|
|
22
|
+
let checkpoint = options.repository.getCheckpoint(options.source.sourceId, options.adapter.adapterVersion);
|
|
23
|
+
const port = {
|
|
24
|
+
getCheckpoint: () => checkpoint,
|
|
25
|
+
getBatchCheckpoint: () => null,
|
|
26
|
+
commitStagedImports: () => 0,
|
|
27
|
+
applyImport: (batch, next) => {
|
|
28
|
+
// Publish through the existing import transaction only with explicit opt-in.
|
|
29
|
+
const receipt = options.repository.applyImport(batch, next, true);
|
|
30
|
+
checkpoint = { ...next };
|
|
31
|
+
for (const event of batch.events) {
|
|
32
|
+
if (event.sessionId === options.sessionId)
|
|
33
|
+
options.onEvent(event);
|
|
34
|
+
}
|
|
35
|
+
return receipt;
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const importer = new IncrementalSessionImporter(port);
|
|
39
|
+
while (!options.signal?.aborted) {
|
|
40
|
+
try {
|
|
41
|
+
if (options.import) {
|
|
42
|
+
await importer.import({
|
|
43
|
+
...options, publish: true, batchRunId: undefined,
|
|
44
|
+
limits: { ...options.limits, batchSize: 1 },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Do not fingerprint or persist snapshots: export adapters may rewrite
|
|
49
|
+
// a JSON document as it grows. Each adapter owns its resume semantics.
|
|
50
|
+
for await (const record of options.adapter.stream(options.selectedSource, checkpoint ? { value: checkpoint.cursor } : undefined)) {
|
|
51
|
+
if (options.signal?.aborted)
|
|
52
|
+
return;
|
|
53
|
+
const serialized = JSON.stringify(record);
|
|
54
|
+
const limits = { ...DEFAULT_IMPORT_BYTE_LIMITS, ...options.limits };
|
|
55
|
+
assertImportByteLimit('maxRecordBytes', limits, Buffer.byteLength(serialized));
|
|
56
|
+
const count = (checkpoint?.recordsRead ?? 0) + 1;
|
|
57
|
+
assertSessionResourceLimit('maxRecords', options.limits?.maxRecords ?? 1_000_000, count);
|
|
58
|
+
const digest = sha256(serialized);
|
|
59
|
+
const runId = sha256([options.source.sourceId, options.adapter.adapterVersion,
|
|
60
|
+
count - 1, count, digest].join('\0'));
|
|
61
|
+
const { events } = normalizeSessionBatch(options, [record], [digest], runId);
|
|
62
|
+
checkpoint = { ...checkpoint, cursor: record.sourceCursor ?? String(count),
|
|
63
|
+
recordsRead: count, bytesRead: (checkpoint?.bytesRead ?? 0) + Buffer.byteLength(serialized) };
|
|
64
|
+
for (const event of events) {
|
|
65
|
+
if (event.sessionId === options.sessionId)
|
|
66
|
+
options.onEvent(event);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (options.signal?.aborted)
|
|
73
|
+
return;
|
|
74
|
+
// Some native JSONL adapters report an in-progress final line explicitly.
|
|
75
|
+
if (!(options.follow && error instanceof SessionContractError
|
|
76
|
+
&& error.code === 'TRUNCATED_SOURCE'))
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
await options.onPoll?.();
|
|
80
|
+
if (!options.follow || options.signal?.aborted)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
await setTimeout(pollMs, undefined, { signal: options.signal });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (!options.signal?.aborted)
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=tail.js.map
|
|
@@ -4,6 +4,8 @@ import { basename, dirname, join, resolve } from 'node:path';
|
|
|
4
4
|
import { inspectMuseNativeWorkspace } from './adapters/muse.js';
|
|
5
5
|
import { readOmpSessionHeader } from './adapters/omp.js';
|
|
6
6
|
import { grokbotConversationIdFromLocator } from './adapters/grokbot-locator.js';
|
|
7
|
+
import { GROK_BUILD_ACTIVE_FRESHNESS_MS, grokBuildLiveState, grokBuildSourceWorkspaceMatch, readGrokBuildActiveRegistry, } from './adapters/grok-build-native.js';
|
|
8
|
+
import { hermesSnapshotStagingDir } from './adapters/hermes-state-db.js';
|
|
7
9
|
import { resolveOmpPaths } from '../providers/omp-paths.mjs';
|
|
8
10
|
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
9
11
|
import { SessionCoveragePreflightSchema, SessionContractError, sha256, } from './contracts.js';
|
|
@@ -15,7 +17,7 @@ import { matchSessionWorkspace } from './workspace-matching.js';
|
|
|
15
17
|
import { matchImportExclusion, nativeSessionIdFromLocator, resolveImportExclusions } from './import-exclusions.js';
|
|
16
18
|
export const DISCOVERY_MANIFEST_VERSION = '1.0.0';
|
|
17
19
|
const MANUAL_EXPORT_PROVIDERS = new Set([
|
|
18
|
-
'copilot', '
|
|
20
|
+
'copilot', 'opencode', 'openclaw', 'openhuman',
|
|
19
21
|
'warp', 'devin-desktop', 'generic',
|
|
20
22
|
]);
|
|
21
23
|
export async function discoverWorkspaceHistories(options) {
|
|
@@ -31,6 +33,8 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
31
33
|
...(options.museRoot ? ['muse'] : []),
|
|
32
34
|
...(options.dshRoot ? ['deepseek-harness'] : []),
|
|
33
35
|
...(options.grokbotRoot ? ['grokbot'] : []),
|
|
36
|
+
...(options.grokRoot ? ['grok-build'] : []),
|
|
37
|
+
...(options.hermesRoot ? ['hermes'] : []),
|
|
34
38
|
];
|
|
35
39
|
const savedRoots = options.authorizedRoots ?? options.refreshFrom?.authorizedRoots;
|
|
36
40
|
const authorizedProviders = options.refreshFrom
|
|
@@ -68,6 +72,15 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
68
72
|
// no-filtering behavior already used for `pi`/`deepseek-harness`).
|
|
69
73
|
{ provider: 'grokbot', roots: options.grokbotRoot
|
|
70
74
|
? [resolve(options.grokbotRoot)] : [] },
|
|
75
|
+
// Grok Build authorizes the sessions directory itself, never Grok home:
|
|
76
|
+
// `<root>/<url-encoded cwd>/<id>/chat_history.jsonl`. No fallback through
|
|
77
|
+
// $HOME, GROK_HOME, or --provider-home grants access.
|
|
78
|
+
{ provider: 'grok-build', roots: options.grokRoot
|
|
79
|
+
? [resolve(options.grokRoot)] : [] },
|
|
80
|
+
// Hermes authorizes `<root>/state.db` for a read-only backup snapshot.
|
|
81
|
+
// Staging joins the scope below; nothing else under the root is read.
|
|
82
|
+
{ provider: 'hermes', roots: options.hermesRoot
|
|
83
|
+
? [resolve(options.hermesRoot)] : [] },
|
|
71
84
|
{
|
|
72
85
|
provider: 'claude',
|
|
73
86
|
// Same scope rule as codex: configured providers keep their root grant
|
|
@@ -155,14 +168,17 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
if (availableRoots.length === 0) {
|
|
158
|
-
const
|
|
171
|
+
const needsExplicitRoot = (entry.provider === 'codex' || entry.provider === 'omp'
|
|
159
172
|
|| entry.provider === 'deepseek-harness' || entry.provider === 'muse'
|
|
160
|
-
|| entry.provider === 'grokbot'
|
|
173
|
+
|| entry.provider === 'grokbot' || entry.provider === 'grok-build'
|
|
174
|
+
|| entry.provider === 'hermes')
|
|
161
175
|
&& entry.roots.length === 0;
|
|
162
|
-
|
|
176
|
+
const expectedFlag = entry.provider === 'deepseek-harness' ? 'dsh' : entry.provider;
|
|
177
|
+
const rootKind = entry.provider === 'hermes' ? 'Hermes home (state.db is snapshotted read-only)' : 'sessions root';
|
|
178
|
+
reports.set(entry.provider, providerReport(entry.provider, needsExplicitRoot ? 'export-required' : 'unavailable', needsExplicitRoot ? 'manual-export' : 'discoverable', [], needsExplicitRoot
|
|
163
179
|
? 'SHARED_ROOT_AUTHORIZATION_REQUIRED'
|
|
164
|
-
: 'PROVIDER_ROOT_UNAVAILABLE',
|
|
165
|
-
? `Pass --${
|
|
180
|
+
: 'PROVIDER_ROOT_UNAVAILABLE', needsExplicitRoot
|
|
181
|
+
? `Pass --${expectedFlag}-root with an explicitly authorized ${rootKind}.`
|
|
166
182
|
: `No authorized ${entry.provider} workspace history root was found.`));
|
|
167
183
|
if (entry.provider === 'codex')
|
|
168
184
|
reports.get(entry.provider).rootCounts = [...rootCounts.values()];
|
|
@@ -173,13 +189,48 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
173
189
|
workspaceId,
|
|
174
190
|
allowedRoots: availableRoots,
|
|
175
191
|
};
|
|
192
|
+
if (entry.provider === 'hermes') {
|
|
193
|
+
// The consistent snapshot is staged under owned workspace staging (the
|
|
194
|
+
// provider home stays read-only); staged locators need scope cover, and
|
|
195
|
+
// refresh must retain both the origin grant and the staging grant.
|
|
196
|
+
const staging = hermesSnapshotStagingDir(workspacePath);
|
|
197
|
+
if (!scope.allowedRoots.includes(staging))
|
|
198
|
+
scope.allowedRoots.push(staging);
|
|
199
|
+
authorizedRoots[entry.provider] = [...new Set([...availableRoots, staging])];
|
|
200
|
+
if (!rootCounts.has(staging)) {
|
|
201
|
+
rootCounts.set(staging, {
|
|
202
|
+
root: redactSourceLocator(staging), rootId: sha256(staging),
|
|
203
|
+
status: 'checked', sourceCount: 0, duplicateCount: 0,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
176
207
|
const providerSources = [];
|
|
177
208
|
let matchedBySubfolder = 0;
|
|
178
209
|
let matchedByWorktree = 0;
|
|
179
210
|
const museSourceInspections = [];
|
|
211
|
+
// Grok Build live detection reads exactly one explicitly authorized file
|
|
212
|
+
// (never the sessions root's parent implicitly); absence means unknown.
|
|
213
|
+
let grokActiveRegistry = null;
|
|
214
|
+
let grokActiveRegistryLoaded = false;
|
|
215
|
+
const grokActiveEvidence = async () => {
|
|
216
|
+
if (grokActiveRegistryLoaded || entry.provider !== 'grok-build' || !options.grokActiveSessions)
|
|
217
|
+
return grokActiveRegistry;
|
|
218
|
+
grokActiveRegistryLoaded = true;
|
|
219
|
+
try {
|
|
220
|
+
grokActiveRegistry = await readGrokBuildActiveRegistry(resolve(options.grokActiveSessions), [resolve(options.grokActiveSessions)].map((file) => dirname(file)));
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
grokActiveRegistry = null;
|
|
224
|
+
}
|
|
225
|
+
return grokActiveRegistry;
|
|
226
|
+
};
|
|
180
227
|
for await (const descriptor of adapter.discover(scope)) {
|
|
181
228
|
const known = options.knownSources?.find(source => source.provider === descriptor.provider && source.locator === descriptor.locator);
|
|
182
|
-
|
|
229
|
+
// A Hermes projection holds many sessions, so its locator basename is
|
|
230
|
+
// never a session identity.
|
|
231
|
+
const nativeSessionId = entry.provider === 'hermes'
|
|
232
|
+
? (known?.nativeSessionId ?? descriptor.nativeSessionId)
|
|
233
|
+
: (known?.nativeSessionId ?? descriptor.nativeSessionId ?? nativeSessionIdFromLocator(descriptor));
|
|
183
234
|
const exclusion = matchImportExclusion({ ...descriptor, nativeSessionId }, exclusions);
|
|
184
235
|
if (exclusion) {
|
|
185
236
|
const locator = resolve(descriptor.locator);
|
|
@@ -224,6 +275,13 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
224
275
|
if (!workspaceMatch)
|
|
225
276
|
continue;
|
|
226
277
|
}
|
|
278
|
+
if (entry.provider === 'grok-build') {
|
|
279
|
+
// The url-encoded cwd directory is the only workspace evidence; an
|
|
280
|
+
// unresolvable or conflicting value never attaches to the caller.
|
|
281
|
+
workspaceMatch = await grokBuildSourceWorkspaceMatch(locator, workspacePath);
|
|
282
|
+
if (!workspaceMatch)
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
227
285
|
const ompHeader = entry.provider === 'omp' ? await readOmpSessionHeader({ ...descriptor, sourceId: 'discovery', authorizedScope: scope }) : undefined;
|
|
228
286
|
if (ompHeader) {
|
|
229
287
|
workspaceMatch = await matchSessionWorkspace(ompHeader.cwd, workspacePath);
|
|
@@ -262,6 +320,24 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
262
320
|
rootCount.sourceCount += 1;
|
|
263
321
|
const source = sourceFromDescriptor(descriptor, locator, authorizedRoot, details, fingerprint.digest);
|
|
264
322
|
source.nativeSessionId = rolloutId ?? ompHeader?.id ?? nativeSessionId;
|
|
323
|
+
if (entry.provider === 'hermes') {
|
|
324
|
+
// A state.db projection holds many sessions; the locator basename is
|
|
325
|
+
// not a session identity.
|
|
326
|
+
delete source.nativeSessionId;
|
|
327
|
+
}
|
|
328
|
+
if (entry.provider === 'grok-build' && source.nativeSessionId) {
|
|
329
|
+
// Provider-reported activity only; histories stay usable without it.
|
|
330
|
+
const observedAt = options.createdAt ?? new Date().toISOString();
|
|
331
|
+
const registry = await grokActiveEvidence();
|
|
332
|
+
const evidence = grokBuildLiveState(registry, source.nativeSessionId);
|
|
333
|
+
source.liveEvidence = {
|
|
334
|
+
...evidence,
|
|
335
|
+
observedAt,
|
|
336
|
+
expiresAt: evidence.state === 'active'
|
|
337
|
+
? new Date(Date.parse(observedAt) + GROK_BUILD_ACTIVE_FRESHNESS_MS).toISOString()
|
|
338
|
+
: null,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
265
341
|
if (ompHeader)
|
|
266
342
|
source.sourceId = sha256(['omp-native-source-v1', workspaceId, ompHeader.id].join('\0'));
|
|
267
343
|
if (entry.provider === 'grokbot') {
|
|
@@ -269,10 +345,13 @@ export async function discoverWorkspaceHistories(options) {
|
|
|
269
345
|
// old entries, so a content-digest sourceId (the default above)
|
|
270
346
|
// would churn every re-discovery and break checkpoint continuity.
|
|
271
347
|
// Key it on the conversation id instead, same rationale as the OMP
|
|
272
|
-
// override above.
|
|
348
|
+
// override above. Unlike OMP, the key carries no workspaceId: Grok
|
|
349
|
+
// Bot conversations are account-scoped (#3232), so the same replica
|
|
350
|
+
// must resolve to one host-catalog source no matter which workspace
|
|
351
|
+
// authorizes it; workspace catalogs keep a reference instead of a copy.
|
|
273
352
|
const conversationId = grokbotConversationIdFromLocator(locator);
|
|
274
353
|
if (conversationId) {
|
|
275
|
-
source.sourceId = sha256(['grokbot-native-source-v1',
|
|
354
|
+
source.sourceId = sha256(['grokbot-native-source-v1', conversationId].join('\0'));
|
|
276
355
|
}
|
|
277
356
|
}
|
|
278
357
|
providerSources.push(source);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Preserve non-portable skill metadata across the portable boundary.
|
|
2
|
+
import { AGENT_SKILLS_SIDECAR_SCHEMA } from './agent-skills.js';
|
|
3
|
+
export function createAgentSkillSidecar(document, provenance, validationProfile, trust) {
|
|
4
|
+
return {
|
|
5
|
+
$schema: AGENT_SKILLS_SIDECAR_SCHEMA,
|
|
6
|
+
schemaVersion: 1,
|
|
7
|
+
aiwg: structuredClone(document.aiwg),
|
|
8
|
+
...(document.providers ? { providers: structuredClone(document.providers) } : {}),
|
|
9
|
+
provenance: structuredClone(provenance),
|
|
10
|
+
validationProfile,
|
|
11
|
+
trust: { ...trust },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Restore AIWG metadata omitted at a strict portability boundary. Portable
|
|
16
|
+
* content remains authoritative for standard fields, body, and resources.
|
|
17
|
+
*/
|
|
18
|
+
export function restoreAgentSkillFromSidecar(standard, body, resources, sidecar) {
|
|
19
|
+
return {
|
|
20
|
+
standard: structuredClone(standard),
|
|
21
|
+
body,
|
|
22
|
+
resources: resources.map((resource) => ({ ...resource })),
|
|
23
|
+
aiwg: structuredClone(sidecar.aiwg),
|
|
24
|
+
...(sidecar.providers ? { providers: structuredClone(sidecar.providers) } : {}),
|
|
25
|
+
unknownFields: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=agent-skill-sidecar.js.map
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @implements #1875
|
|
8
8
|
*/
|
|
9
|
+
import { CLAUDE_SKILL_FIELDS } from './provider-extensions.js';
|
|
10
|
+
export { CLAUDE_SKILL_FIELDS } from './provider-extensions.js';
|
|
11
|
+
export { createAgentSkillSidecar, restoreAgentSkillFromSidecar } from './agent-skill-sidecar.js';
|
|
9
12
|
export const AGENT_SKILLS_BASELINE = {
|
|
10
13
|
repository: 'agentskills/agentskills',
|
|
11
14
|
revision: '38a2ff82958afee88dadf4831509e6f7e9d8ef4e',
|
|
@@ -174,6 +177,7 @@ export function validateCompatibleAgentSkillMetadata(frontmatter, directoryName,
|
|
|
174
177
|
const recognizedFields = new Set([
|
|
175
178
|
...STANDARD_SKILL_FIELDS,
|
|
176
179
|
...AIWG_SKILL_CONTROL_FIELDS,
|
|
180
|
+
...CLAUDE_SKILL_FIELDS,
|
|
177
181
|
]);
|
|
178
182
|
for (const key of Object.keys(frontmatter).sort()) {
|
|
179
183
|
if (!recognizedFields.has(key)) {
|
|
@@ -223,27 +227,4 @@ export function equivalentPortableAllowedTools(aiwg) {
|
|
|
223
227
|
}
|
|
224
228
|
return aiwg.allowedTools.join(' ');
|
|
225
229
|
}
|
|
226
|
-
export function createAgentSkillSidecar(document, provenance, validationProfile, trust) {
|
|
227
|
-
return {
|
|
228
|
-
$schema: AGENT_SKILLS_SIDECAR_SCHEMA,
|
|
229
|
-
schemaVersion: 1,
|
|
230
|
-
aiwg: structuredClone(document.aiwg),
|
|
231
|
-
provenance: structuredClone(provenance),
|
|
232
|
-
validationProfile,
|
|
233
|
-
trust: { ...trust },
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Restore AIWG metadata omitted at a strict portability boundary. Portable
|
|
238
|
-
* content remains authoritative for standard fields, body, and resources.
|
|
239
|
-
*/
|
|
240
|
-
export function restoreAgentSkillFromSidecar(standard, body, resources, sidecar) {
|
|
241
|
-
return {
|
|
242
|
-
standard: structuredClone(standard),
|
|
243
|
-
body,
|
|
244
|
-
resources: resources.map((resource) => ({ ...resource })),
|
|
245
|
-
aiwg: structuredClone(sidecar.aiwg),
|
|
246
|
-
unknownFields: [],
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
230
|
//# sourceMappingURL=agent-skills.js.map
|