@ijfw/memory-server 1.4.4 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
- package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
- package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
- package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
- package/package.json +1 -1
- package/src/active-extension-writer.js +144 -64
- package/src/api-client.js +43 -5
- package/src/audit-roster.js +80 -5
- package/src/blackboard.js +298 -6
- package/src/cli-run.js +33 -5
- package/src/codex-agents.js +96 -5
- package/src/cost/aggregator.js +39 -9
- package/src/cost/pricing.js +57 -0
- package/src/cost/readers/gemini.js +1 -1
- package/src/cross-audit-chunker.js +189 -0
- package/src/cross-dispatcher.js +124 -21
- package/src/cross-orchestrator-cli.js +550 -14
- package/src/cross-orchestrator.js +1016 -17
- package/src/cross-project-search.js +195 -9
- package/src/dashboard-client-waves.html +304 -0
- package/src/dashboard-client.html +5 -1
- package/src/dashboard-server.js +73 -0
- package/src/deploy-alerts.js +150 -0
- package/src/design/iframe-bridge.js +242 -0
- package/src/design-companion.js +144 -0
- package/src/dispatch/checkpoint-cli.js +97 -0
- package/src/dispatch/colon-syntax.js +81 -1
- package/src/dispatch/extension.js +26 -2
- package/src/dispatch/registry-cli.js +4 -1
- package/src/dispatch/wave-cli.js +201 -6
- package/src/dispatch/worktree-cli.js +40 -0
- package/src/dispatch-planner.js +97 -2
- package/src/dream/runner.mjs +47 -11
- package/src/dream/stage-runner.js +40 -0
- package/src/dream/state-file.js +102 -0
- package/src/extension-installer.js +70 -24
- package/src/extension-quota-tracker.js +4 -2
- package/src/extension-registry.js +289 -35
- package/src/feedback-detector.js +26 -0
- package/src/fs-lock.js +259 -7
- package/src/gate-result.js +95 -1
- package/src/hero-line.js +86 -5
- package/src/intent-router.js +35 -0
- package/src/lib/a11y-contract.js +117 -0
- package/src/lib/atomic-io.js +29 -8
- package/src/lib/cache-keepalive.js +150 -0
- package/src/lib/jsonl-rotation.js +104 -0
- package/src/lib/lighthouse-pillar.js +121 -0
- package/src/lib/llm-call.js +121 -0
- package/src/lib/playwright-baseline.js +205 -0
- package/src/lib/rekor-bridge.js +221 -0
- package/src/lib/repo-map.js +392 -0
- package/src/lib/shasum-verify.js +164 -0
- package/src/lib/sketches-gc.js +132 -0
- package/src/lib/tmp-suffix.js +62 -0
- package/src/lib/ui-review-runner.js +554 -0
- package/src/lib/uispec-drift.js +301 -0
- package/src/lib/uispec-intake.js +381 -0
- package/src/lib/worktree-guards.js +118 -0
- package/src/lib/worktree-recovery.js +100 -0
- package/src/memory/auto-linker.js +152 -0
- package/src/memory/benchmark.js +498 -0
- package/src/memory/dedup.js +126 -0
- package/src/memory/embedding-cache.js +136 -0
- package/src/memory/fact-extractor.js +168 -0
- package/src/memory/fts5.js +65 -1
- package/src/memory/migrations/004-bitemporal.js +91 -0
- package/src/memory/migrations/005-vector-cache.js +61 -0
- package/src/memory/migrations/006-obsidian-graph.js +46 -0
- package/src/memory/migrations/007-skill-telemetry.js +24 -0
- package/src/memory/migrations/008-write-provenance.js +41 -0
- package/src/memory/obsidian-parser.js +91 -0
- package/src/memory/query-dataview.js +86 -0
- package/src/memory/search.js +10 -0
- package/src/memory/temporal.js +529 -0
- package/src/memory/tokenize.js +10 -0
- package/src/memory-facts-handler.js +37 -0
- package/src/memory-feedback.js +260 -2
- package/src/model-refresh.js +292 -0
- package/src/observability/cost-anomaly.js +166 -0
- package/src/observability/evaluator-checkpoint-contract.js +117 -0
- package/src/observability/trace-id.js +163 -0
- package/src/orchestrator/agents-md-blackboard.js +152 -0
- package/src/orchestrator/checkpoint-contract.md +140 -0
- package/src/orchestrator/debug-trident.js +570 -0
- package/src/orchestrator/merge-block-aware.js +350 -0
- package/src/orchestrator/plan-checker.js +475 -0
- package/src/orchestrator/post-done-runner.js +249 -0
- package/src/orchestrator/review.js +38 -3
- package/src/orchestrator/runtime-loop.js +430 -0
- package/src/orchestrator/skill-telemetry-sink.js +29 -0
- package/src/orchestrator/skill-telemetry.js +37 -0
- package/src/orchestrator/state-events.js +459 -0
- package/src/orchestrator/state-sdk.js +1764 -0
- package/src/orchestrator/status-protocol.js +84 -17
- package/src/orchestrator/subagent-telemetry.js +452 -0
- package/src/orchestrator/termination.js +160 -0
- package/src/orchestrator/verification-gate.js +200 -16
- package/src/orchestrator/wave-state.js +332 -23
- package/src/orchestrator/worktree-provision.js +77 -0
- package/src/override-use-registry.js +111 -5
- package/src/receipts.js +36 -4
- package/src/recovery/checkpoint.js +56 -3
- package/src/recovery/code-fixer.js +656 -0
- package/src/recovery/truncation.js +317 -0
- package/src/redactor.js +75 -6
- package/src/runtime-mediator.js +15 -0
- package/src/sanitizer.js +10 -0
- package/src/search-hybrid.js +139 -0
- package/src/server.js +603 -59
- package/src/swarm/worktree.js +27 -4
- package/src/swarm-config.js +94 -17
- package/src/team/domain-templates/book.json +51 -0
- package/src/team/domain-templates/business.json +41 -0
- package/src/team/domain-templates/content.json +50 -0
- package/src/team/domain-templates/design.json +44 -0
- package/src/team/domain-templates/research.json +41 -0
- package/src/team/domain-templates/software.json +40 -0
- package/src/team/generator.js +278 -3
- package/src/team/modify.js +203 -0
- package/src/team/schemas.js +48 -0
- package/src/update-apply.js +19 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// IJFW v1.5.0 -- per-stage error-isolated dream runner.
|
|
2
|
+
//
|
|
3
|
+
// Wayland's "best-effort staged" pattern: a failure in one stage logs and
|
|
4
|
+
// continues; downstream stages still execute. The state file records each
|
|
5
|
+
// stage's status so a future run (or operator) can see what actually
|
|
6
|
+
// happened, and which stages need re-execution.
|
|
7
|
+
//
|
|
8
|
+
// Lift from sibling project Wayland:
|
|
9
|
+
// crates/wcore-memory/src/consolidate.rs ConsolidationEngine::run
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
markRunStart, markStageStarted, markStageCompleted,
|
|
13
|
+
markStageFailed, markRunCompleted,
|
|
14
|
+
} from './state-file.js';
|
|
15
|
+
|
|
16
|
+
export async function runStages(root, stages) {
|
|
17
|
+
markRunStart(root);
|
|
18
|
+
const completed = [];
|
|
19
|
+
const failed = [];
|
|
20
|
+
for (const stage of stages) {
|
|
21
|
+
if (!stage || typeof stage.name !== 'string' || typeof stage.run !== 'function') {
|
|
22
|
+
failed.push({ name: String(stage?.name), reason: 'invalid_stage_definition' });
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
markStageStarted(root, stage.name);
|
|
26
|
+
try {
|
|
27
|
+
const extras = (await stage.run()) || {};
|
|
28
|
+
markStageCompleted(root, stage.name, extras);
|
|
29
|
+
completed.push({ name: stage.name, extras });
|
|
30
|
+
} catch (e) {
|
|
31
|
+
markStageFailed(root, stage.name, e?.message || String(e));
|
|
32
|
+
failed.push({ name: stage.name, reason: e?.message || String(e) });
|
|
33
|
+
// CONTINUE -- per-stage isolation.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
markRunCompleted(root);
|
|
37
|
+
return { ok: failed.length === 0, completed, failed };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default { runStages };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// IJFW v1.5.0 -- dream-cycle state file (Wayland pattern + idempotency).
|
|
2
|
+
//
|
|
3
|
+
// Lives at `<repoRoot>/.ijfw/.dream-state-v2.json` (legacy cooldown.js owns
|
|
4
|
+
// `.dream-state.json` and writes an ISO-string last_run_at incompatible
|
|
5
|
+
// with this module's numeric unix-ms; using a separate path avoids the
|
|
6
|
+
// schema collision while letting both layers coexist).
|
|
7
|
+
//
|
|
8
|
+
// Tracks:
|
|
9
|
+
// - last_run_at: unix-ms timestamp of last completed dream cycle (idle gate)
|
|
10
|
+
// - runs_total: cumulative count
|
|
11
|
+
// - stages: per-stage status for the current/most-recent run
|
|
12
|
+
//
|
|
13
|
+
// The legacy cooldown.markCompleted() is still called as the final stage
|
|
14
|
+
// in runner.mjs so any downstream code reading the old marker keeps working.
|
|
15
|
+
// This module is the additive layer.
|
|
16
|
+
//
|
|
17
|
+
// Pattern lift from sibling project Wayland's
|
|
18
|
+
// `crates/wcore-memory/src/consolidate.rs` DreamThrottle.
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
existsSync, readFileSync, writeFileSync, mkdirSync, renameSync,
|
|
22
|
+
} from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
|
|
25
|
+
const DEFAULT = { version: 1, last_run_at: null, runs_total: 0, stages: {} };
|
|
26
|
+
|
|
27
|
+
function pathOf(root) {
|
|
28
|
+
return join(root, '.ijfw', '.dream-state-v2.json');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function readDreamState(root) {
|
|
32
|
+
const p = pathOf(root);
|
|
33
|
+
if (!existsSync(p)) return { ...DEFAULT };
|
|
34
|
+
try {
|
|
35
|
+
const obj = JSON.parse(readFileSync(p, 'utf8'));
|
|
36
|
+
return { ...DEFAULT, ...obj, stages: obj?.stages || {} };
|
|
37
|
+
} catch {
|
|
38
|
+
// Corrupt file treated as no-record — safer than blocking.
|
|
39
|
+
return { ...DEFAULT };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function writeDreamState(root, state) {
|
|
44
|
+
const p = pathOf(root);
|
|
45
|
+
mkdirSync(join(root, '.ijfw'), { recursive: true });
|
|
46
|
+
const tmp = p + '.tmp';
|
|
47
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2));
|
|
48
|
+
renameSync(tmp, p);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function markStageStarted(root, stage) {
|
|
52
|
+
const s = readDreamState(root);
|
|
53
|
+
s.stages[stage] = { status: 'in_progress', started_at: Date.now() };
|
|
54
|
+
writeDreamState(root, s);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function markStageCompleted(root, stage, extras = {}) {
|
|
58
|
+
const s = readDreamState(root);
|
|
59
|
+
s.stages[stage] = {
|
|
60
|
+
...s.stages[stage],
|
|
61
|
+
status: 'completed',
|
|
62
|
+
completed_at: Date.now(),
|
|
63
|
+
...extras,
|
|
64
|
+
};
|
|
65
|
+
writeDreamState(root, s);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function markStageFailed(root, stage, reason) {
|
|
69
|
+
const s = readDreamState(root);
|
|
70
|
+
s.stages[stage] = {
|
|
71
|
+
...s.stages[stage],
|
|
72
|
+
status: 'failed',
|
|
73
|
+
failed_at: Date.now(),
|
|
74
|
+
reason: String(reason || 'unknown'),
|
|
75
|
+
};
|
|
76
|
+
writeDreamState(root, s);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function shouldRunNow(root, { min_idle_minutes = 30 } = {}) {
|
|
80
|
+
const s = readDreamState(root);
|
|
81
|
+
if (s.last_run_at == null) return true;
|
|
82
|
+
return (Date.now() - s.last_run_at) / 60000 >= min_idle_minutes;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function markRunStart(root) {
|
|
86
|
+
const s = readDreamState(root);
|
|
87
|
+
s.stages = {};
|
|
88
|
+
writeDreamState(root, s);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function markRunCompleted(root) {
|
|
92
|
+
const s = readDreamState(root);
|
|
93
|
+
s.last_run_at = Date.now();
|
|
94
|
+
s.runs_total = (s.runs_total || 0) + 1;
|
|
95
|
+
writeDreamState(root, s);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export default {
|
|
99
|
+
readDreamState, writeDreamState,
|
|
100
|
+
markStageStarted, markStageCompleted, markStageFailed,
|
|
101
|
+
shouldRunNow, markRunStart, markRunCompleted,
|
|
102
|
+
};
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
removeExtensionFromAgentsMd,
|
|
58
58
|
uninstallExtensionSkillsFromPlatforms,
|
|
59
59
|
} from '../../installer/src/install-helpers.js';
|
|
60
|
+
import { recordDeployFailure } from './deploy-alerts.js';
|
|
60
61
|
|
|
61
62
|
// --- constants -------------------------------------------------------------
|
|
62
63
|
|
|
@@ -337,33 +338,34 @@ function spawnChecked(cmd, args, opts = {}) {
|
|
|
337
338
|
|
|
338
339
|
/**
|
|
339
340
|
* Pre-scan a tarball for tar-slip / symlink / hardlink members before
|
|
340
|
-
* extraction.
|
|
341
|
-
* `tar -tvzf`, reject any member that
|
|
341
|
+
* extraction. Reject any member that
|
|
342
342
|
* - starts with `/` (absolute path)
|
|
343
343
|
* - contains a `..` segment (escapes extract dir on join)
|
|
344
344
|
* - is a symlink or hardlink (mode char `l` or `h` in the verbose listing)
|
|
345
345
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
346
|
+
* v1.5.0 audit-LOW-update-#16: consolidated to a SINGLE `tar -tzf` listing.
|
|
347
|
+
* Plain `-tzf` gives the per-member name directly (one line per member, no
|
|
348
|
+
* metadata fields, identical across BSD + GNU tar) which removes the brittle
|
|
349
|
+
* verbose-field parsing entirely. Symlink/hardlink rejection now relies on
|
|
350
|
+
* post-extract verification (lstat against extracted members in
|
|
351
|
+
* extractTarball) -- safer than parsing a non-portable `ls -l` style listing
|
|
352
|
+
* and removes one fork+pipe per install.
|
|
350
353
|
*
|
|
351
354
|
* @param {string} tarballPath
|
|
352
355
|
* @returns {Promise<void>} resolves if clean, throws with reason if not
|
|
353
356
|
*/
|
|
354
357
|
async function preflightTarball(tarballPath) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
//
|
|
360
|
-
// no leading metadata. Used for path-traversal / absolute-path checks where
|
|
361
|
-
// robust field parsing across BSD vs GNU tar is otherwise brittle.
|
|
358
|
+
// v1.5.0 audit-LOW-update-#16: single `tar -tzf` listing feeds both the
|
|
359
|
+
// path-traversal scan AND the symlink/hardlink scan (via the ` -> ` /
|
|
360
|
+
// ` link to ` separators that every mainstream tar emits). The verbose
|
|
361
|
+
// `-tvzf` pass below is a defense-in-depth fallback for BSD-tar variants
|
|
362
|
+
// that don't emit those separators in the bare listing.
|
|
362
363
|
const { stdout: namesOut } = await spawnChecked('tar', ['-tzf', tarballPath], {
|
|
363
364
|
timeoutMs: GIT_CLONE_TIMEOUT_MS,
|
|
364
365
|
captureStdout: true,
|
|
365
366
|
});
|
|
366
367
|
const names = namesOut.split('\n').filter((l) => l.length > 0);
|
|
368
|
+
let sawLinkArrow = false;
|
|
367
369
|
for (const name of names) {
|
|
368
370
|
if (name.startsWith('/')) {
|
|
369
371
|
throw new Error(`tar-slip: tarball contains absolute path member: ${name}`);
|
|
@@ -372,17 +374,31 @@ async function preflightTarball(tarballPath) {
|
|
|
372
374
|
if (segments.includes('..')) {
|
|
373
375
|
throw new Error(`tar-slip: tarball contains '..' segment in member: ${name}`);
|
|
374
376
|
}
|
|
377
|
+
// Symlink detection direct from `-tzf` — most tar builds print
|
|
378
|
+
// `<name> -> <target>` on symlink members; hardlinks print
|
|
379
|
+
// `<name> link to <target>`. Either form refused unconditionally.
|
|
380
|
+
if (name.includes(' -> ') || name.includes(' link to ')) {
|
|
381
|
+
sawLinkArrow = true;
|
|
382
|
+
throw new Error(`tar-slip: tarball contains symlink/hardlink (refused): ${name.slice(0, 200)}`);
|
|
383
|
+
}
|
|
375
384
|
}
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
const
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
// Defense-in-depth (v1.5.0 audit-LOW-update-#16 fold-in): if a BSD-tar
|
|
386
|
+
// variant doesn't emit ` -> ` markers in `-tzf`, fall back to one `-tvzf`
|
|
387
|
+
// pass to catch the type char. Runs ONLY when the cheap scan saw no arrow
|
|
388
|
+
// markers — common case (mainstream tar) avoids the second spawn.
|
|
389
|
+
if (!sawLinkArrow) {
|
|
390
|
+
const { stdout: verboseOut } = await spawnChecked('tar', ['-tvzf', tarballPath], {
|
|
391
|
+
timeoutMs: GIT_CLONE_TIMEOUT_MS,
|
|
392
|
+
captureStdout: true,
|
|
393
|
+
});
|
|
394
|
+
const lines = verboseOut.split('\n').filter((l) => l.length > 0);
|
|
395
|
+
for (const line of lines) {
|
|
396
|
+
const modeMatch = line.match(/^(\S+)/);
|
|
397
|
+
if (!modeMatch) continue;
|
|
398
|
+
const typeChar = modeMatch[1][0];
|
|
399
|
+
if (typeChar === 'l' || typeChar === 'h') {
|
|
400
|
+
throw new Error(`tar-slip: tarball contains symlink/hardlink (refused): ${line.slice(0, 200)}`);
|
|
401
|
+
}
|
|
386
402
|
}
|
|
387
403
|
}
|
|
388
404
|
}
|
|
@@ -1032,8 +1048,14 @@ export async function installExtension(source, opts = {}) {
|
|
|
1032
1048
|
// Project scope only — org/user scopes deploy lazily at session start
|
|
1033
1049
|
// via override-resolver. Failures here do NOT unwind the install (the
|
|
1034
1050
|
// extension is already registered); they surface as deploy_partial.
|
|
1051
|
+
//
|
|
1052
|
+
// v1.5.0 audit-MED-update-M8 (F-REL-2): partial-deploy failures now also
|
|
1053
|
+
// persist to `~/.ijfw/state/deploy-failures.jsonl` so the next memory
|
|
1054
|
+
// prelude surfaces them. Without this, a half-deployed extension was
|
|
1055
|
+
// only visible in the immediate install reply.
|
|
1035
1056
|
let deployInfo;
|
|
1036
1057
|
let deployPartial = false;
|
|
1058
|
+
const partialDeployFailures = [];
|
|
1037
1059
|
if (opts.scope === 'project') {
|
|
1038
1060
|
try {
|
|
1039
1061
|
const skillList = Array.isArray(manifest.skills) ? manifest.skills : [];
|
|
@@ -1048,7 +1070,16 @@ export async function installExtension(source, opts = {}) {
|
|
|
1048
1070
|
failed: d.failed,
|
|
1049
1071
|
receiptPath: d.receiptPath,
|
|
1050
1072
|
};
|
|
1051
|
-
if (Array.isArray(d.failed) && d.failed.length > 0)
|
|
1073
|
+
if (Array.isArray(d.failed) && d.failed.length > 0) {
|
|
1074
|
+
deployPartial = true;
|
|
1075
|
+
for (const f of d.failed) {
|
|
1076
|
+
partialDeployFailures.push({
|
|
1077
|
+
platform: f && f.platform ? String(f.platform) : 'unknown',
|
|
1078
|
+
skillName: f && f.skillName ? String(f.skillName) : null,
|
|
1079
|
+
error: f && f.error ? String(f.error) : 'unknown',
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1052
1083
|
} catch (err) {
|
|
1053
1084
|
const msg = err && err.message ? err.message : String(err);
|
|
1054
1085
|
process.stderr.write(
|
|
@@ -1056,6 +1087,7 @@ export async function installExtension(source, opts = {}) {
|
|
|
1056
1087
|
);
|
|
1057
1088
|
deployPartial = true;
|
|
1058
1089
|
deployInfo = { deployed: [], failed: [{ platform: '*', skillName: '*', error: msg }] };
|
|
1090
|
+
partialDeployFailures.push({ platform: '*', skillName: '*', error: msg });
|
|
1059
1091
|
}
|
|
1060
1092
|
try {
|
|
1061
1093
|
await deployExtensionToAgentsMd(
|
|
@@ -1069,6 +1101,20 @@ export async function installExtension(source, opts = {}) {
|
|
|
1069
1101
|
`[ijfw] extension-installer: AGENTS.md inject failed for ${manifest.name}: ${msg}\n`,
|
|
1070
1102
|
);
|
|
1071
1103
|
deployPartial = true;
|
|
1104
|
+
partialDeployFailures.push({ platform: 'AGENTS.md', skillName: null, error: msg });
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// M8 — surface partial-deploy state for the next prelude.
|
|
1109
|
+
if (deployPartial && partialDeployFailures.length > 0) {
|
|
1110
|
+
try {
|
|
1111
|
+
await recordDeployFailure({
|
|
1112
|
+
extension: manifest.name,
|
|
1113
|
+
scope: opts.scope,
|
|
1114
|
+
failures: partialDeployFailures,
|
|
1115
|
+
});
|
|
1116
|
+
} catch {
|
|
1117
|
+
// Best-effort: alert path failure must not break install.
|
|
1072
1118
|
}
|
|
1073
1119
|
}
|
|
1074
1120
|
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
24
24
|
import { homedir } from 'node:os';
|
|
25
25
|
import { join, dirname } from 'node:path';
|
|
26
|
-
import { randomBytes } from 'node:crypto';
|
|
27
26
|
|
|
28
27
|
import { withFsLock } from './fs-lock.js';
|
|
28
|
+
// v1.5.0 audit-LOW-update-#13: shared tmp-suffix helper.
|
|
29
|
+
import { tmpSuffix } from './lib/tmp-suffix.js';
|
|
29
30
|
|
|
30
31
|
const STATE_REL = ['.ijfw', 'state', 'extension-quotas.json'];
|
|
31
32
|
|
|
@@ -105,7 +106,8 @@ export async function writeQuotaState(home, state) {
|
|
|
105
106
|
const h = home || homeFromOpts({});
|
|
106
107
|
const path = statePath(h);
|
|
107
108
|
await mkdir(dirname(path), { recursive: true });
|
|
108
|
-
|
|
109
|
+
// v1.5.0 audit-LOW-update-#13: tmpSuffix() replaces inline randomBytes call.
|
|
110
|
+
const tmp = `${path}.tmp.${tmpSuffix({ bytes: 4, includePid: false })}`;
|
|
109
111
|
await writeFile(tmp, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
110
112
|
await rename(tmp, path);
|
|
111
113
|
}
|