create-pmos 0.134.0 → 0.135.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/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/payload/planning/evals/eval-calibration.md +1 -1
- package/payload/planning/okrs/health-budgets.md +1 -1
- package/payload/state/discoveries.md +1 -1
- package/workspace/bin/notes-replay.js +235 -0
- package/workspace/c3-briefing-test.js +130 -0
- package/workspace/c3-foundation-test.js +159 -0
- package/workspace/c3-ib-items-test.js +105 -0
- package/workspace/c3-ib-more-test.js +120 -0
- package/workspace/c3-ib-triage-backlog-test.js +119 -0
- package/workspace/c3-ini-harness.js +66 -0
- package/workspace/c3-initiatives-test.js +140 -0
- package/workspace/c3-one-home-test.js +14 -1
- package/workspace/c3-reports-test.js +57 -18
- package/workspace/c3-settings-test.js +139 -0
- package/workspace/c3-work-test.js +42 -10
- package/workspace/fixtures/one-home-baseline.json +1 -9
- package/workspace/guide/briefing-cards.md +15 -3
- package/workspace/guide/briefing.md +12 -8
- package/workspace/guide/calendar-popup.md +2 -2
- package/workspace/guide/console-calendar.md +39 -10
- package/workspace/guide/index.md +1 -0
- package/workspace/guide/initiative-boards.md +65 -0
- package/workspace/guide/jira.md +3 -1
- package/workspace/guide/layout.md +2 -0
- package/workspace/guide/my-work.md +5 -3
- package/workspace/guide/notes.md +16 -1
- package/workspace/guide/reports-dashboards.md +29 -5
- package/workspace/guide/settings.md +68 -0
- package/workspace/guide/welcome.md +10 -10
- package/workspace/lib/calendar-cli.js +3 -2
- package/workspace/lib/calendar-layers.js +21 -10
- package/workspace/lib/calendar-tidy.js +230 -0
- package/workspace/lib/engine/boards.js +542 -0
- package/workspace/lib/engine/breakdown-ops.js +180 -0
- package/workspace/lib/engine/calendar-import.js +84 -0
- package/workspace/lib/engine/calendar-model.js +185 -0
- package/workspace/lib/engine/card-state.js +92 -0
- package/workspace/lib/engine/derived/derive.js +92 -0
- package/workspace/lib/engine/derived/plan.js +3 -0
- package/workspace/lib/engine/derived/trust.js +3 -0
- package/workspace/lib/engine/doc-health.js +142 -0
- package/workspace/lib/engine/doc-sheet.js +75 -0
- package/workspace/lib/engine/intake.js +78 -0
- package/workspace/lib/engine/outbox.js +86 -0
- package/workspace/lib/engine/plan-forecast.js +342 -0
- package/workspace/lib/engine/promises.js +29 -0
- package/workspace/lib/engine/records/index.js +114 -0
- package/workspace/lib/engine/releases.js +96 -0
- package/workspace/lib/engine/replies.js +99 -0
- package/workspace/lib/engine/reply-contract/reply-contract.js +372 -0
- package/workspace/lib/engine/report-families.js +175 -0
- package/workspace/lib/engine/roadmap-ops.js +150 -0
- package/workspace/lib/engine/sessions-list.js +27 -0
- package/workspace/lib/engine/signal-rules.js +95 -0
- package/workspace/lib/engine/signals/cards.js +57 -0
- package/workspace/lib/engine/signals/detectors.js +370 -0
- package/workspace/lib/engine/signals/facts-cli.js +8 -0
- package/workspace/lib/engine/signals/facts.js +197 -0
- package/workspace/lib/engine/signals/index.js +33 -0
- package/workspace/lib/engine/signals/kinds.js +110 -0
- package/workspace/lib/engine/signals/layout-paths.js +44 -0
- package/workspace/lib/engine/signals/options.js +24 -0
- package/workspace/lib/engine/signals/score.js +72 -0
- package/workspace/lib/engine/signals/select.js +47 -0
- package/workspace/lib/engine/step-planner.js +65 -0
- package/workspace/lib/engine/trust/claim-rules.json +93 -0
- package/workspace/lib/engine/trust/import-calendar.js +21 -0
- package/workspace/lib/engine/trust/import-guard.js +36 -0
- package/workspace/lib/engine/trust/ledger.js +53 -0
- package/workspace/lib/engine/trust/outbox.js +45 -0
- package/workspace/lib/engine/trust/plan.js +5 -0
- package/workspace/lib/engine/trust/trust.js +21 -0
- package/workspace/lib/engine/truth-table.js +57 -0
- package/workspace/lib/engine/workspace-lib.js +24 -0
- package/workspace/lib/factory-join.js +1 -1
- package/workspace/lib/facts-async.js +4 -4
- package/workspace/lib/headless.js +95 -0
- package/workspace/lib/import-overlay.js +2 -1
- package/workspace/lib/jira-status.js +30 -3
- package/workspace/lib/notes-capture.js +9 -1
- package/workspace/lib/notes-final.js +38 -13
- package/workspace/lib/notes-replay.js +125 -0
- package/workspace/lib/notes-screen.js +180 -0
- package/workspace/lib/notes-whisper.js +10 -3
- package/workspace/lib/outbox-store.js +10 -6
- package/workspace/lib/pmos-verbs.js +3 -1
- package/workspace/lib/proactive-cli.js +7 -0
- package/workspace/lib/proactive.js +15 -2
- package/workspace/lib/repo-script.js +36 -0
- package/workspace/lib/report-dashboards.js +5 -2
- package/workspace/lib/report-frame.js +122 -0
- package/workspace/lib/report-schedules.js +113 -0
- package/workspace/lib/route-modules.js +9 -2
- package/workspace/lib/routes-boards.js +734 -0
- package/workspace/lib/routes-brief-page.js +13 -4
- package/workspace/lib/routes-briefing.js +22 -11
- package/workspace/lib/routes-c3-init.js +4 -3
- package/workspace/lib/routes-c3-life.js +2 -1
- package/workspace/lib/routes-c3-reports.js +96 -13
- package/workspace/lib/routes-c3-strategy.js +4 -3
- package/workspace/lib/routes-c3-timeline.js +3 -2
- package/workspace/lib/routes-calendar-import.js +3 -2
- package/workspace/lib/routes-calendar-views.js +9 -4
- package/workspace/lib/routes-doc-health.js +2 -1
- package/workspace/lib/routes-doc-sheet.js +2 -1
- package/workspace/lib/routes-forecast.js +2 -1
- package/workspace/lib/routes-hooks.js +3 -2
- package/workspace/lib/routes-initiatives.js +2 -1
- package/workspace/lib/routes-intake.js +2 -1
- package/workspace/lib/routes-notes.js +55 -9
- package/workspace/lib/routes-outbox.js +23 -3
- package/workspace/lib/routes-pm-items.js +19 -6
- package/workspace/lib/routes-records.js +3 -2
- package/workspace/lib/routes-releases.js +2 -1
- package/workspace/lib/routes-report-facts.js +4 -3
- package/workspace/lib/routes-report-families.js +2 -1
- package/workspace/lib/routes-roadmap-ops.js +5 -4
- package/workspace/lib/routes-runs.js +6 -5
- package/workspace/lib/routes-settings.js +51 -0
- package/workspace/lib/routes-signals.js +3 -2
- package/workspace/lib/routes-stage-tools.js +220 -0
- package/workspace/lib/run-answers.js +2 -1
- package/workspace/lib/settings-store.js +226 -0
- package/workspace/lib/stage-tools.js +208 -0
- package/workspace/lib/vendor/scripts/signals/layout-paths.js +2 -1
- package/workspace/lib/vendor/scripts/workspace-lib.js +24 -0
- package/workspace/lib/work-evidence.js +73 -0
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/briefing-deck.js +63 -17
- package/workspace/public/pm/briefing-model.js +36 -7
- package/workspace/public/pm/c3-ambient.js +100 -0
- package/workspace/public/pm/c3-brief-needs.js +26 -6
- package/workspace/public/pm/c3-brief-pulse.js +12 -4
- package/workspace/public/pm/c3-brief-today.js +56 -21
- package/workspace/public/pm/c3-brief.js +2 -1
- package/workspace/public/pm/c3-calendar.js +161 -42
- package/workspace/public/pm/c3-calpop.js +5 -5
- package/workspace/public/pm/c3-demo-boards.js +649 -0
- package/workspace/public/pm/c3-demo-brief-page.js +1 -1
- package/workspace/public/pm/c3-demo-cal4.js +3 -2
- package/workspace/public/pm/c3-demo-cal5.js +52 -0
- package/workspace/public/pm/c3-demo-data.js +17 -27
- package/workspace/public/pm/c3-demo-rep3.js +46 -5
- package/workspace/public/pm/c3-demo.js +2 -1
- package/workspace/public/pm/c3-dock.js +1 -1
- package/workspace/public/pm/c3-ib-docs.js +80 -0
- package/workspace/public/pm/c3-ib-initiatives.js +134 -0
- package/workspace/public/pm/c3-ib-items.js +201 -0
- package/workspace/public/pm/c3-ib-releases.js +51 -0
- package/workspace/public/pm/c3-ib-sprints.js +55 -0
- package/workspace/public/pm/c3-ib-triage.js +178 -0
- package/workspace/public/pm/c3-ii-tabs.js +240 -0
- package/workspace/public/pm/c3-ini-records.js +197 -0
- package/workspace/public/pm/c3-ini-timeline.js +278 -0
- package/workspace/public/pm/c3-ini-ui.js +229 -0
- package/workspace/public/pm/c3-ini-work.js +139 -0
- package/workspace/public/pm/c3-initiatives.css +218 -0
- package/workspace/public/pm/c3-initiatives.js +741 -0
- package/workspace/public/pm/c3-plan.js +2 -2
- package/workspace/public/pm/c3-reports.css +47 -47
- package/workspace/public/pm/c3-reports.js +223 -185
- package/workspace/public/pm/c3-settings.css +74 -0
- package/workspace/public/pm/c3-settings.js +703 -0
- package/workspace/public/pm/c3-shell.js +102 -36
- package/workspace/public/pm/c3-terminal-icons.js +2 -2
- package/workspace/public/pm/c3-terminal.js +2 -2
- package/workspace/public/pm/calendar-views.js +1 -1
- package/workspace/public/pm/components.css +319 -213
- package/workspace/public/pm/console3.css +99 -23
- package/workspace/public/pm/dest-notes.js +26 -4
- package/workspace/public/pm/notes-screen-capture.js +165 -0
- package/workspace/public/pm/notes-screen-view.js +98 -0
- package/workspace/public/pm/panel-trees.js +5 -2
- package/workspace/public/pm/pm-work-view.js +233 -146
- package/workspace/public/pm/shell-bell.js +3 -2
- package/workspace/public/pm/shell-recording.js +29 -8
- package/workspace/public/pm/tokens.css +2 -2
- package/workspace/public/pm/view-notes.js +46 -9
- package/workspace/public/pm/welcome.js +197 -89
- package/workspace/public/welcome.html +6 -4
- package/workspace/server.js +105 -6
- package/workspace/skills/day-check.skill +1 -1
- package/workspace/skills/jira-sync.skill +6 -4
- package/workspace/skills/morning-brief.skill +1 -1
- package/workspace/test-files.json +12 -3
- package/workspace/test-lists/c3-ib-items.json +9 -0
- package/workspace/test-lists/c3-ib-more.json +9 -0
- package/workspace/test-lists/c3-ib-triage-backlog.json +9 -0
- package/workspace/test-lists/c3-initiative-boards.json +6 -0
- package/workspace/test-lists/calendar-tidy-up.json +6 -0
- package/workspace/test-lists/console3-briefing.json +9 -0
- package/workspace/test-lists/console3-foundation.json +9 -0
- package/workspace/test-lists/console3-reports.json +3 -3
- package/workspace/test-lists/meeting-notes.json +10 -4
- package/workspace/test-lists/workspace-headless-jobs.json +5 -0
- package/workspace/test-lists/workspace-package-script-closure.json +6 -0
- package/workspace/lib/vendor/workspace/lib/actor.js +0 -91
- package/workspace/lib/vendor/workspace/lib/board-cache.js +0 -156
- package/workspace/lib/vendor/workspace/lib/console-state.js +0 -295
- package/workspace/lib/vendor/workspace/lib/factory-join.js +0 -158
- package/workspace/lib/vendor/workspace/lib/git-info.js +0 -306
- package/workspace/lib/vendor/workspace/lib/plan-items.js +0 -922
- package/workspace/lib/vendor/workspace/lib/plan-tree.js +0 -419
- package/workspace/lib/vendor/workspace/lib/review-gate.js +0 -39
- package/workspace/lib/vendor/workspace/lib/sink.js +0 -321
- package/workspace/lib/vendor/workspace/lib/state-root.js +0 -587
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/* routes-notes.js — the Notes view's routes (meeting-notes, Runs 1 to 4; lib/route-modules.js).
|
|
3
3
|
*
|
|
4
4
|
* GET /api/notes → { ok, notes: [row] } newest first; a row never carries the body
|
|
5
|
-
* GET /api/notes/one?id=… → { ok, note, bullets_state, audio, recording } imports an agent summary and the agent's
|
|
5
|
+
* GET /api/notes/one?id=… → { ok, note, bullets_state, audio, recording, screen: { frames: [{ t, file, w, h, bytes, why?, text? }] } } imports an agent summary and the agent's
|
|
6
6
|
* minute bullets written since the last read (Run 8). note.bullets:
|
|
7
7
|
* [{ id, t, minute, text, owner, cat, cat_by, by: 'agent', open?, check?, dropped? }];
|
|
8
8
|
* an open bullet has cat null. bullets_state: { agent: 'on'|'off'|'failed',
|
|
@@ -35,11 +35,18 @@
|
|
|
35
35
|
* POST /api/notes/line { id, index, cat?, summary?, tags?, people?, text? } → { ok, segment, summary } the PM's correction (Run 3)
|
|
36
36
|
* GET /api/notes/recording?id=… → audio/wav, with byte ranges the note's kept audio (Run 3, D92)
|
|
37
37
|
* POST /api/notes/recording-delete { id } → { ok } deletes the note's audio and keeps the rest
|
|
38
|
+
* POST /api/notes/screen { id, cap, t, img, w, h, why? } → { ok, frame } one screenshot of the shared screen (notes-screen-capture): img is the
|
|
39
|
+
* picture as base64 (JPEG, PNG or WebP, 2 MB at most), t its meeting time in ms, cap the capture's token.
|
|
40
|
+
* Refused: a private note (409), a note not listening (409), "Capture the shared screen" switched off (409),
|
|
41
|
+
* a token that is not the live capture's (409), a bad t or picture (400), a full note (409)
|
|
42
|
+
* GET /api/notes/screen?id=…&t=… → image/jpeg|png|webp the shot kept at meeting time t; 404 for a private note or no such shot
|
|
43
|
+
* POST /api/notes/screen-delete { id } → { ok, deleted } deletes the note's screenshots and keeps the rest
|
|
38
44
|
* POST /api/notes/clean-audio { id } → { ok, started } makes the cleaned copy of the replay now (Run 3 of notes-audio-quality; needs ffmpeg)
|
|
39
45
|
* GET /api/notes/engine?ws=… → { ok, whisper, jev } is Whisper set up; which Jev gates hold (booleans only)
|
|
40
46
|
* POST /api/notes/switch { ws, jev?, check? } → { ok, jev } the two D91 switches, default off
|
|
41
|
-
* { language?, model?, accuracy?, keep_audio? } → { ok, whisper } the language, the model, the decoding
|
|
42
|
-
* ('fast' | 'accurate') and
|
|
47
|
+
* { language?, model?, accuracy?, keep_audio?, screen? } → { ok, whisper } the language, the model, the decoding
|
|
48
|
+
* ('fast' | 'accurate'), keeping audio and capturing the shared screen, for this machine.
|
|
49
|
+
* whisper.screen is that switch; whisper.ocr says whether tesseract was found
|
|
43
50
|
* POST /api/notes/whisper-setup { ws? } → { ok, started, session, command } a terminal with the setup typed in, not run
|
|
44
51
|
* POST /api/notes/private { id, private } → { ok, private }
|
|
45
52
|
* POST /api/notes/summarize { id, ws?, prefer? } → { ok, summary, initiative, source, agent? } the local summary; with ws and
|
|
@@ -78,6 +85,7 @@ const whisper = require('./notes-whisper.js');
|
|
|
78
85
|
const notesCapture = require('./notes-capture.js');
|
|
79
86
|
const notesRepo = require('./notes-repo.js');
|
|
80
87
|
const notesAudio = require('./notes-audio.js');
|
|
88
|
+
const notesScreen = require('./notes-screen.js');
|
|
81
89
|
const notesEcho = require('./notes-echo.js');
|
|
82
90
|
const notesPolish = require('./notes-polish.js');
|
|
83
91
|
const notesFinal = require('./notes-final.js');
|
|
@@ -95,7 +103,7 @@ const VOCAB_TTL_MS = 60 * 1000;
|
|
|
95
103
|
const prefixes = ['/api/notes'];
|
|
96
104
|
const POSTS = new Set(['/api/notes/new', '/api/notes/save', '/api/notes/dictate', '/api/notes/record', '/api/notes/transcript',
|
|
97
105
|
'/api/notes/private', '/api/notes/pause', '/api/notes/stop', '/api/notes/summarize', '/api/notes/summarize-agent', '/api/notes/delete', '/api/notes/audio', '/api/notes/label',
|
|
98
|
-
'/api/notes/speaker', '/api/notes/switch', '/api/notes/whisper-setup', '/api/notes/final', '/api/notes/final-setup', '/api/notes/line', '/api/notes/recording-delete', '/api/notes/clean-audio', '/api/notes/link', '/api/notes/bullet', '/api/notes/highlight', '/api/notes/highlight-remove', '/api/notes/final-edit', '/api/notes/wispr-fetch']);
|
|
106
|
+
'/api/notes/speaker', '/api/notes/switch', '/api/notes/whisper-setup', '/api/notes/final', '/api/notes/final-setup', '/api/notes/line', '/api/notes/recording-delete', '/api/notes/screen', '/api/notes/screen-delete', '/api/notes/clean-audio', '/api/notes/link', '/api/notes/bullet', '/api/notes/highlight', '/api/notes/highlight-remove', '/api/notes/final-edit', '/api/notes/wispr-fetch']);
|
|
99
107
|
|
|
100
108
|
/* ── labels and checks, one note at a time ───────────────────────────────────────────────────── */
|
|
101
109
|
const chains = new Map(); // note id → the promise of its work so far (minute batches, imports, labels, checks)
|
|
@@ -278,8 +286,12 @@ function handToAgent(ctx, id, note, b) {
|
|
|
278
286
|
const h = notes.minuteHandoff(dir, { id, minute: b.minute, lines: b.lines, context: b.context, final: b.final, terms: repoTermsFor(ctx, note) });
|
|
279
287
|
if (!h.ok) return false;
|
|
280
288
|
if (m.agent === 'on' && m.session && typeof ctx.sendToSession === 'function' && ctx.sendToSession(m.session, notes.bulletsFollowUp(h))) return true;
|
|
289
|
+
// workspace-headless-jobs R6: a headless bullets run takes no follow-up. While one is still working, this minute waits
|
|
290
|
+
// for the next run, which writes every earlier minute that has no bullets yet. The last minute never waits.
|
|
291
|
+
if (m.agent === 'on' && m.session && !b.final && typeof ctx.headlessBusy === 'function' && ctx.headlessBusy(m.session)) return true;
|
|
281
292
|
const w = note.ws ? ctx.getWs(note.ws) : null;
|
|
282
|
-
const s = ctx.spawnSession({ name: 'notes: bullets', agent: cmd, ws: w ? w.id : undefined, origin: 'notes-bullets:' + id, initialInput: notes.bulletsPrompt(h) + '\r'
|
|
293
|
+
const s = ctx.spawnSession({ name: 'notes: bullets', agent: cmd, ws: w ? w.id : undefined, origin: 'notes-bullets:' + id, initialInput: notes.bulletsPrompt(h) + '\r',
|
|
294
|
+
addDirs: [h.folder] });
|
|
283
295
|
const started = !!s && s.state !== 'exited';
|
|
284
296
|
if (!started) {
|
|
285
297
|
try { fs.unlinkSync(h.md); } catch { /* gone */ }
|
|
@@ -422,7 +434,7 @@ function engineInfo(ctx, w) {
|
|
|
422
434
|
const out = { ready: live.ready, live: live.ready ? live.engine : null, missing: d.missing, setup: d.setup, model_dir: d.model_dir, model_size: d.model_size,
|
|
423
435
|
language: whisper.getLanguage(ctx.runtimeDir), languages: whisper.LANGUAGES.map(([value, label]) => ({ value, label })),
|
|
424
436
|
model: whisper.getModel(ctx.runtimeDir), models: whisper.MODELS.map((m) => ({ value: m.id, label: m.label })),
|
|
425
|
-
accuracy: whisper.getAccuracy(ctx.runtimeDir), accuracies: whisper.ACCURACIES.map((a) => ({ value: a.id, label: a.label })), keep_audio: notesAudio.getKeep(ctx.runtimeDir), clean_audio: notesAudio.getClean(ctx.runtimeDir), ffmpeg: !!notesPolish.detect(),
|
|
437
|
+
accuracy: whisper.getAccuracy(ctx.runtimeDir), accuracies: whisper.ACCURACIES.map((a) => ({ value: a.id, label: a.label })), keep_audio: notesAudio.getKeep(ctx.runtimeDir), clean_audio: notesAudio.getClean(ctx.runtimeDir), ffmpeg: !!notesPolish.detect(), screen: notesScreen.getOn(ctx.runtimeDir), ocr: !!notesScreen.ocrDetect(),
|
|
426
438
|
final: finalInfo(ctx), wispr: wisprOn(ctx) };
|
|
427
439
|
if (!w) return { whisper: out, jev: null };
|
|
428
440
|
const repo = tsSettings.getRepo(ctx.runtimeDir, w.root);
|
|
@@ -615,12 +627,21 @@ async function handle(req, res, url, ctx) {
|
|
|
615
627
|
if (r.ok && mergeWispr(ctx, nid)) r = notes.read(dir, nid, { resumeMs: resumeMsOf(ctx) });
|
|
616
628
|
if (r.ok) {
|
|
617
629
|
r.audio = { pending: transcriber ? transcriber.pending() : 0, error: audioErrors.get(nid) || null }; r.recording = notesAudio.info(dir, nid); if (r.recording) r.recording = { ...r.recording, cleaned: notesAudio.hasClean(dir, nid) };
|
|
630
|
+
r.screen = { frames: r.note.private ? [] : notesScreen.list(dir, nid) };
|
|
618
631
|
if (!Array.isArray(r.note.bullets)) r.note.bullets = [];
|
|
619
632
|
r.bullets_state = bulletsState(ctx, r.note);
|
|
620
633
|
}
|
|
621
634
|
send(ctx, res, r);
|
|
622
635
|
return true;
|
|
623
636
|
}
|
|
637
|
+
if (p === '/api/notes/screen' && (req.method === 'GET' || req.method === 'HEAD')) {
|
|
638
|
+
const nid = url.searchParams.get('id');
|
|
639
|
+
const r = notes.read(dir, nid);
|
|
640
|
+
if (!r.ok) { send(ctx, res, r); return true; }
|
|
641
|
+
if (r.note.private) { ctx.json(res, { ok: false, error: 'this note is private and keeps no screenshots' }, 404); return true; }
|
|
642
|
+
notesScreen.serve(req, res, dir, nid, url.searchParams.get('t'));
|
|
643
|
+
return true;
|
|
644
|
+
}
|
|
624
645
|
if (p === '/api/notes/recording' && (req.method === 'GET' || req.method === 'HEAD')) {
|
|
625
646
|
const nid = url.searchParams.get('id');
|
|
626
647
|
const r = notes.read(dir, nid);
|
|
@@ -801,6 +822,23 @@ async function handle(req, res, url, ctx) {
|
|
|
801
822
|
if (!r.ok) return done(r);
|
|
802
823
|
return done({ ok: true, deleted: notesAudio.remove(dir, id) });
|
|
803
824
|
}
|
|
825
|
+
if (p === '/api/notes/screen') {
|
|
826
|
+
const note = notes.read(dir, id);
|
|
827
|
+
if (!note.ok) return done(note);
|
|
828
|
+
if (note.note.private) return done({ ok: false, status: 409, error: 'this note is marked private, so nothing more is kept in it' });
|
|
829
|
+
if (!note.note.recorded_from) return done({ ok: false, status: 409, error: 'press Listen first' });
|
|
830
|
+
if (!notesScreen.getOn(dir)) return done({ ok: false, status: 409, error: 'Capture the shared screen is switched off in Settings' });
|
|
831
|
+
const own = notesCapture.owns(dir, id, { token: body.cap });
|
|
832
|
+
if (!own.ok) return done(own);
|
|
833
|
+
if (typeof body.t !== 'number' || !Number.isFinite(body.t) || body.t < 0) return done({ ok: false, status: 400, error: 'a screenshot needs its meeting time' });
|
|
834
|
+
if (typeof body.img !== 'string' || !body.img) return done({ ok: false, status: 400, error: 'there is no picture to keep' });
|
|
835
|
+
return done(notesScreen.add(dir, { id, t: body.t, buf: Buffer.from(body.img, 'base64'), w: body.w, h: body.h, why: body.why }));
|
|
836
|
+
}
|
|
837
|
+
if (p === '/api/notes/screen-delete') {
|
|
838
|
+
const r = notes.read(dir, id);
|
|
839
|
+
if (!r.ok) return done(r);
|
|
840
|
+
return done({ ok: true, deleted: notesScreen.remove(dir, id) });
|
|
841
|
+
}
|
|
804
842
|
if (p === '/api/notes/clean-audio') {
|
|
805
843
|
// the PM asks for the cleaned copy of one note now; the work goes on in the background and the replay changes when it ends
|
|
806
844
|
const r = notes.read(dir, id);
|
|
@@ -817,6 +855,13 @@ async function handle(req, res, url, ctx) {
|
|
|
817
855
|
if (body.wispr !== undefined) {
|
|
818
856
|
if (typeof body.wispr !== 'boolean') { ctx.json(res, { ok: false, error: 'say wispr: true or wispr: false' }, 400); return true; }
|
|
819
857
|
setWisprOn(dir, body.wispr);
|
|
858
|
+
if (body.language === undefined && body.model === undefined && body.accuracy === undefined && body.keep_audio === undefined && body.clean_audio === undefined && body.jev === undefined && body.check === undefined && body.screen === undefined) {
|
|
859
|
+
ctx.json(res, { ok: true, ...engineInfo(ctx, o.ok ? o.w : null) }); return true;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
if (body.screen !== undefined) {
|
|
863
|
+
const sc = notesScreen.setOn(dir, body.screen);
|
|
864
|
+
if (!sc.ok) { ctx.json(res, { ok: false, error: sc.error }, 400); return true; }
|
|
820
865
|
if (body.language === undefined && body.model === undefined && body.accuracy === undefined && body.keep_audio === undefined && body.clean_audio === undefined && body.jev === undefined && body.check === undefined) {
|
|
821
866
|
ctx.json(res, { ok: true, ...engineInfo(ctx, o.ok ? o.w : null) }); return true;
|
|
822
867
|
}
|
|
@@ -860,7 +905,7 @@ async function handle(req, res, url, ctx) {
|
|
|
860
905
|
if (p === '/api/notes/private') {
|
|
861
906
|
vocabCache.delete(id);
|
|
862
907
|
const r = notes.setPrivate(dir, { id, private: body.private });
|
|
863
|
-
if (r.ok && r.private) { clearExpiry(id); disarm(id); unwatchLate(id); cancelFinal(ctx, id); unwatchWispr(id); if (transcriber) transcriber.drop(id); notesAudio.remove(dir, id); notesCapture.remove(dir, id); }
|
|
908
|
+
if (r.ok && r.private) { clearExpiry(id); disarm(id); unwatchLate(id); cancelFinal(ctx, id); unwatchWispr(id); if (transcriber) transcriber.drop(id); notesAudio.remove(dir, id); notesCapture.remove(dir, id); notesScreen.remove(dir, id); }
|
|
864
909
|
return done(r);
|
|
865
910
|
}
|
|
866
911
|
if (p === '/api/notes/summarize') {
|
|
@@ -889,7 +934,7 @@ async function handle(req, res, url, ctx) {
|
|
|
889
934
|
}
|
|
890
935
|
return done(r);
|
|
891
936
|
}
|
|
892
|
-
if (p === '/api/notes/delete') { clearExpiry(id); swept.delete(id); disarm(id); unwatchLate(id); cancelFinal(ctx, id); unwatchWispr(id); if (transcriber) transcriber.drop(id); const r = notes.remove(dir, id); if (r.ok) { notesAudio.remove(dir, id); notesCapture.remove(dir, id); } return done(r); }
|
|
937
|
+
if (p === '/api/notes/delete') { clearExpiry(id); swept.delete(id); disarm(id); unwatchLate(id); cancelFinal(ctx, id); unwatchWispr(id); if (transcriber) transcriber.drop(id); const r = notes.remove(dir, id); if (r.ok) { notesAudio.remove(dir, id); notesCapture.remove(dir, id); notesScreen.remove(dir, id); } return done(r); }
|
|
893
938
|
if (p === '/api/notes/highlight') {
|
|
894
939
|
// the PM's highlight: the transcript of the window, then Jev's label and the repository check of its text
|
|
895
940
|
const r = notes.addHighlight(dir, { id, from: body.from, to: body.to });
|
|
@@ -938,7 +983,8 @@ function startAgent(ctx, dir, id, w) {
|
|
|
938
983
|
notes.agentStarted(dir, { id, started: false, reason: 'this workspace cannot start sessions' });
|
|
939
984
|
return { ok: true, started: false, session: null, reason: 'this workspace cannot start sessions', handoff: h.handoff, out: h.out, withheld: h.withheld };
|
|
940
985
|
}
|
|
941
|
-
const s = ctx.spawnSession({ name: 'notes: summary', agent: 'claude', ws: w ? w.id : undefined, origin: 'notes-summary:' + id, initialInput: h.prompt + '\r'
|
|
986
|
+
const s = ctx.spawnSession({ name: 'notes: summary', agent: 'claude', ws: w ? w.id : undefined, origin: 'notes-summary:' + id, initialInput: h.prompt + '\r',
|
|
987
|
+
addDirs: [...new Set([path.dirname(h.handoff), path.dirname(h.out)])] });
|
|
942
988
|
const started = !!s && s.state !== 'exited';
|
|
943
989
|
const reason = started ? '' : ((s && s.note) || 'could not start a claude session');
|
|
944
990
|
notes.agentStarted(dir, { id, session: s ? s.id : null, started, reason });
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* claims: { ok, refused: [line] }, last_result }], channels }
|
|
6
6
|
* POST /api/outbox/draft { channel, recipient, subject?, body } → { ok, entry } origin is 'pm' (a page press)
|
|
7
7
|
* POST /api/outbox/edit { id, body } → { ok, entry } a new hash; the old hash is void
|
|
8
|
+
* POST /api/outbox/discard { id, undo? } → { ok } hides a draft (undo: true brings it back); a sent entry is refused
|
|
8
9
|
* POST /api/outbox/confirm { id, hash, reason? } → { ok:false, reason:'no channel connected' } entry stays queued
|
|
9
10
|
* → 422 { ok:false, state:'blocked', refused } when a claim fails the trust rules
|
|
10
11
|
* and no written reason is given; with a reason it is an override (recorded)
|
|
@@ -17,7 +18,8 @@
|
|
|
17
18
|
* The handlers are pure over (req, body, runtimeDir) so a test needs no server.
|
|
18
19
|
*/
|
|
19
20
|
const guard = require('./pm-guard.js');
|
|
20
|
-
const
|
|
21
|
+
const { repoScript } = require('./repo-script.js');
|
|
22
|
+
const outbox = require(repoScript('outbox.js'));
|
|
21
23
|
const store = require('./outbox-store.js');
|
|
22
24
|
const crypto = require('crypto');
|
|
23
25
|
|
|
@@ -57,6 +59,24 @@ function edit(req, body, runtimeDir, now = new Date()) {
|
|
|
57
59
|
return { status: 200, body: { ok: true, entry: view({ ...e, last_result: null }), voided_hash: cur.hash } };
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/* Discard one draft from the queue (Reports > Drafts), or take the discard back (undo: true). The line stays in the log; the fold hides the entry.
|
|
63
|
+
* A sent entry is not discarded: it is a record of what left. */
|
|
64
|
+
function discard(req, body, runtimeDir, now = new Date()) {
|
|
65
|
+
const bad = guarded(req, body); if (bad) return bad;
|
|
66
|
+
const all = store.readLines(store.outboxFile(runtimeDir));
|
|
67
|
+
const id = String(body.id || '');
|
|
68
|
+
if (body.undo === true) {
|
|
69
|
+
if (!all.some((l) => l && l.id === id && l.event === 'discarded')) return fail(404, 'no discarded outbox entry with that id');
|
|
70
|
+
store.appendEvent(runtimeDir, { event: 'restored', id }, now);
|
|
71
|
+
return { status: 200, body: { ok: true, id, restored: true } };
|
|
72
|
+
}
|
|
73
|
+
const cur = store.readOutbox(runtimeDir).find((e) => e.id === id);
|
|
74
|
+
if (!cur) return fail(404, 'no such outbox entry');
|
|
75
|
+
if (cur.state === 'sent') return fail(409, 'a sent entry is a record and is not discarded');
|
|
76
|
+
store.appendEvent(runtimeDir, { event: 'discarded', id }, now);
|
|
77
|
+
return { status: 200, body: { ok: true, id, discarded: true } };
|
|
78
|
+
}
|
|
79
|
+
|
|
60
80
|
function confirm(req, body, runtimeDir, now = new Date(), senders) {
|
|
61
81
|
const bad = guarded(req, body); if (bad) return bad;
|
|
62
82
|
const cur = store.readOutbox(runtimeDir).find((e) => e.id === body.id);
|
|
@@ -78,11 +98,11 @@ function confirm(req, body, runtimeDir, now = new Date(), senders) {
|
|
|
78
98
|
return { status: 200, body: { ok: true, hash: r.hash, override: r.override, state: 'sent' } };
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
const ROUTES = { 'POST /api/outbox/draft': draft, 'POST /api/outbox/edit': edit, 'POST /api/outbox/confirm': confirm };
|
|
101
|
+
const ROUTES = { 'POST /api/outbox/draft': draft, 'POST /api/outbox/edit': edit, 'POST /api/outbox/discard': discard, 'POST /api/outbox/confirm': confirm };
|
|
82
102
|
|
|
83
103
|
module.exports = {
|
|
84
104
|
prefixes: ['/api/outbox'],
|
|
85
|
-
claimCheck, list, draft, edit, confirm, ROUTES,
|
|
105
|
+
claimCheck, list, draft, edit, discard, confirm, ROUTES,
|
|
86
106
|
handle: async (req, res, url, ctx) => {
|
|
87
107
|
const p = url.pathname;
|
|
88
108
|
if (p === '/api/outbox' && req.method === 'GET') { const r = list(ctx.runtimeDir); ctx.json(res, r.body, r.status); return true; }
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* GET /api/pm-items → { ok, today, lists: { waiting, today, week, triage, later }, jobs, items,
|
|
6
6
|
* work: { inbox, today, upcoming, waiting, someday } open items, placed once here
|
|
7
7
|
* review, review_days: 7 open items untouched 7 days, most stale first
|
|
8
|
-
* focus: { suggestions, accepted } at most 3 suggestions: { key, title, why, item_ids, job }
|
|
8
|
+
* focus: { suggestions, accepted, dismissed } at most 3 suggestions: { key, title, why, item_ids, job }; dismissed = the keys marked not today
|
|
9
9
|
* counts: { inbox, today, upcoming, waiting, someday, review } }
|
|
10
10
|
* POST /api/pm-items/add { line } → { ok, item } one line: the parser (lib/pm-items.js) reads the parts
|
|
11
11
|
* POST /api/pm-items/edit { id, fields }→ { ok, item } fields: title, due, priority, waiting_on, job,
|
|
@@ -45,7 +45,9 @@ const guard = require('./pm-guard.js');
|
|
|
45
45
|
const pmItems = require('./pm-items.js');
|
|
46
46
|
const jobs = require('./jobs-to-be-done.js');
|
|
47
47
|
const workFocus = require('./work-focus.js');
|
|
48
|
+
const workEvidence = require('./work-evidence.js');
|
|
48
49
|
const steps = require('./item-steps.js');
|
|
50
|
+
const { repoScript } = require('./repo-script.js');
|
|
49
51
|
|
|
50
52
|
const FIELDS = ['title', 'due', 'priority', 'waiting_on', 'job', 'defer', 'flagged', 'resolves', 'kind', 'person', 'evidence', 'outcome'];
|
|
51
53
|
const STRING_OR_NULL = ['resolves', 'person', 'evidence', 'outcome'];
|
|
@@ -75,10 +77,11 @@ function resolvesTitles(items, ctx, url) {
|
|
|
75
77
|
} catch { return {}; }
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
function view(itemLines, jobLines, now, stepLines = [], focusLines = []) {
|
|
80
|
+
function view(itemLines, jobLines, now, stepLines = [], focusLines = [], proof = null) {
|
|
79
81
|
const today = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
80
82
|
const byItem = steps.foldSteps(stepLines).byItem;
|
|
81
|
-
const
|
|
83
|
+
const folded = pmItems.fold(itemLines).map((i) => ({ ...i, steps: byItem[i.id] || [] }));
|
|
84
|
+
const items = proof ? workEvidence.withEvidence(folded, { ...proof, now: now.getTime() }) : folded;
|
|
82
85
|
const currentJobs = jobs.foldJobs(jobLines).map((j) => ({ ...j, ...jobs.progress(j.id, items) }));
|
|
83
86
|
const lists = Object.fromEntries(pmItems.LISTS.map((l) => [l, []]));
|
|
84
87
|
for (const it of items) lists[pmItems.placement(it, today)].push(it);
|
|
@@ -88,11 +91,21 @@ function view(itemLines, jobLines, now, stepLines = [], focusLines = []) {
|
|
|
88
91
|
for (const l of pmItems.WORK_LISTS) work[l] = pmItems.sortWorkList(l, work[l], today);
|
|
89
92
|
const review = pmItems.reviewQueue(items, now);
|
|
90
93
|
const state = workFocus.foldFocus(focusLines, todayIso);
|
|
91
|
-
const focus = { suggestions: workFocus.focusSuggestions(work.today, currentJobs, state, todayIso), accepted: state.accepted };
|
|
94
|
+
const focus = { suggestions: workFocus.focusSuggestions(work.today, currentJobs, state, todayIso), accepted: state.accepted, dismissed: [...state.dismissed] };
|
|
92
95
|
const counts = { ...Object.fromEntries(pmItems.WORK_LISTS.map((l) => [l, work[l].length])), review: review.length };
|
|
93
96
|
return { ok: true, today: todayIso, lists, jobs: currentJobs, items, work, review, review_days: pmItems.REVIEW_DAYS, focus, counts };
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
// The calendar events and notes that can prove an offline item happened. Read only when an open offline item exists. Best effort: a failed read gives no proof.
|
|
100
|
+
function proofSources(ctx, itemLines) {
|
|
101
|
+
if (!ctx.runtimeDir || !pmItems.fold(itemLines).some((i) => i.kind === 'offline' && !i.done)) return null;
|
|
102
|
+
let events = [];
|
|
103
|
+
let notes = [];
|
|
104
|
+
try { events = require('./routes-calendar-import.js').readEvents(ctx.runtimeDir); } catch { /* no calendar cache */ }
|
|
105
|
+
try { const l = require('./notes.js').list(ctx.runtimeDir); notes = l && l.ok ? l.notes : []; } catch { /* no notes */ }
|
|
106
|
+
return { events, notes };
|
|
107
|
+
}
|
|
108
|
+
|
|
96
109
|
// Each operation returns { status, body, events } and writes nothing itself; the caller appends `events`.
|
|
97
110
|
function checkFields(fields, allowed) {
|
|
98
111
|
if (!fields || typeof fields !== 'object') return 'fields must be an object';
|
|
@@ -226,7 +239,7 @@ function planSteps(b, itemLines) {
|
|
|
226
239
|
// With an agent's reply (from the reply path), parse it: a step that sends something to a person is forced to
|
|
227
240
|
// agent-drafts by the planner. Without one, fall back to the single review step for the PM.
|
|
228
241
|
if (b.reply !== undefined) {
|
|
229
|
-
const plan = require('
|
|
242
|
+
const plan = require(repoScript('step-planner.js')).parsePlanReply(b.reply);
|
|
230
243
|
if (!plan.ok) return { status: 400, body: { ok: false, error: plan.error } };
|
|
231
244
|
const added = plan.steps.map((st) => steps.addStep(b.id, st.text, st.who, newId('step')));
|
|
232
245
|
return { status: 200, body: { ok: true, steps: added.map((r) => ({ id: r.event.id, text: r.event.text, who: r.event.who })) }, events: added.map((r) => r.event) };
|
|
@@ -299,7 +312,7 @@ module.exports = {
|
|
|
299
312
|
const p = url.pathname;
|
|
300
313
|
if (p === '/api/pm-items' && req.method === 'GET') {
|
|
301
314
|
const c = prepare(ctx);
|
|
302
|
-
const v = view(c.itemLines, c.jobLines, c.now, c.stepLines, c.focusLines);
|
|
315
|
+
const v = view(c.itemLines, c.jobLines, c.now, c.stepLines, c.focusLines, proofSources(ctx, c.itemLines));
|
|
303
316
|
v.resolves_titles = resolvesTitles(v.items, ctx, url);
|
|
304
317
|
ctx.json(res, v);
|
|
305
318
|
return true;
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const guard = require('./pm-guard.js');
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const { repoScript } = require('./repo-script.js');
|
|
15
|
+
const records = require(repoScript('records/index.js'));
|
|
16
|
+
const ledger = require(repoScript('trust/ledger.js'));
|
|
16
17
|
|
|
17
18
|
const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join(ctx.repoRoot || path.join(__dirname, '..', '..'), 'planning', 'records');
|
|
18
19
|
const UI = () => ledger.actorFrom({ ui: true });
|
|
@@ -18,7 +18,8 @@ const fs = require('fs');
|
|
|
18
18
|
const path = require('path');
|
|
19
19
|
const crypto = require('crypto');
|
|
20
20
|
const guard = require('./pm-guard.js');
|
|
21
|
-
const
|
|
21
|
+
const { repoScript } = require('./repo-script.js');
|
|
22
|
+
const R = require(repoScript('releases.js'));
|
|
22
23
|
|
|
23
24
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
24
25
|
const dirOf = (ctx) => path.join(ctx.runtimeDir, 'releases');
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* Read-only. The repository read is slow (it derives each initiative's stage from git), so a successful read is kept 5 minutes. */
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const
|
|
17
|
-
const
|
|
16
|
+
const { repoScript } = require('./repo-script.js');
|
|
17
|
+
const RF = require(repoScript('report-families.js'));
|
|
18
|
+
const { foldReleases } = require(repoScript('releases.js'));
|
|
18
19
|
|
|
19
20
|
const prefixes = ['/api/report-families/facts'];
|
|
20
21
|
const TTL = 5 * 60 * 1000;
|
|
@@ -24,7 +25,7 @@ function repoInis(root, opts) {
|
|
|
24
25
|
const hit = memo.get(root);
|
|
25
26
|
const now = (opts && opts.clock) ? opts.clock() : Date.now();
|
|
26
27
|
if (hit && now - hit.at < TTL) return hit.inis;
|
|
27
|
-
const { readRepoFacts } = require('
|
|
28
|
+
const { readRepoFacts } = require(repoScript('signals/facts.js'));
|
|
28
29
|
const inis = (readRepoFacts(root, {}).inis || []).map((i) => ({ id: i.id, title: i.title, promised: i.promised, done: !!i.done }));
|
|
29
30
|
memo.set(root, { at: now, inis });
|
|
30
31
|
return inis;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* POST /api/report-families/check { family, variant?, sections, known_keys?, absent_keys?, stale_keys? }
|
|
6
6
|
* → { ok, blocked, reasons, warnings, sections, ... }
|
|
7
7
|
* Pure. The check reads nothing from disk and writes nothing. There is no send and no issue route in this wave. */
|
|
8
|
-
const
|
|
8
|
+
const { repoScript } = require('./repo-script.js');
|
|
9
|
+
const RF = require(repoScript('report-families.js'));
|
|
9
10
|
|
|
10
11
|
const prefixes = ['/api/report-families'];
|
|
11
12
|
const strs = (v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : undefined);
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
21
|
const guard = require('./pm-guard.js');
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
22
|
+
const { repoScript } = require('./repo-script.js');
|
|
23
|
+
const records = require(repoScript('records/index.js'));
|
|
24
|
+
const ledger = require(repoScript('trust/ledger.js'));
|
|
25
|
+
const roadmap = require(repoScript('roadmap-ops.js'));
|
|
26
|
+
const breakdown = require(repoScript('breakdown-ops.js'));
|
|
26
27
|
|
|
27
28
|
const REPO = path.join(__dirname, '..', '..');
|
|
28
29
|
const rootOf = (ctx) => (ctx && ctx.recordsDir) || path.join((ctx && ctx.repoRoot) || REPO, 'planning', 'records');
|
|
@@ -13,11 +13,12 @@ const path = require('path');
|
|
|
13
13
|
const guard = require('./pm-guard.js');
|
|
14
14
|
const actor = require('./actor.js');
|
|
15
15
|
const runAnswers = require('./run-answers.js');
|
|
16
|
-
const {
|
|
17
|
-
const {
|
|
18
|
-
const {
|
|
19
|
-
const
|
|
20
|
-
const
|
|
16
|
+
const { repoScript } = require('./repo-script.js');
|
|
17
|
+
const { groupSessions } = require(repoScript('sessions-list.js'));
|
|
18
|
+
const { cardFor, correctionEvent } = require(repoScript('card-state.js'));
|
|
19
|
+
const { readLines, runFor, foldReplies } = require(repoScript('replies.js'));
|
|
20
|
+
const C = require(repoScript('reply-contract/reply-contract.js'));
|
|
21
|
+
const { appendOutbox } = require(repoScript('outbox.js'));
|
|
21
22
|
|
|
22
23
|
const prefixes = ['/api/runs'];
|
|
23
24
|
const RUN_RE = /^sess-\d+$/;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/* routes-settings.js — the console3 settings store over HTTP (initiative console3-settings).
|
|
3
|
+
*
|
|
4
|
+
* GET /api/settings?ws=… → { ok, settings, overrides, categories, state_dir }
|
|
5
|
+
* POST /api/settings { key, value, ws } → { ok, key, value } page press; 400 for a value the setting refuses
|
|
6
|
+
* POST /api/settings { key, reset: true, ws } → { ok, key, value } back to the default
|
|
7
|
+
*
|
|
8
|
+
* Only the settings in lib/settings-store.js live here. A setting with a store of its own (the recording language, Online,
|
|
9
|
+
* the TypeSafe key, validate consent, profiles) keeps its own route, and the Settings page calls that one. Writes pass
|
|
10
|
+
* pm-guard requirePagePress, so an agent token is refused. Nothing here makes a network request. */
|
|
11
|
+
const store = require('./settings-store.js');
|
|
12
|
+
const guard = require('./pm-guard.js');
|
|
13
|
+
|
|
14
|
+
/** The workspace a request names, only when it is registered under exactly that id (getWs falls back to the home one). */
|
|
15
|
+
function wsOf(ctx, id) {
|
|
16
|
+
if (id == null || id === '') return null;
|
|
17
|
+
const w = typeof ctx.getWs === 'function' ? ctx.getWs(String(id)) : null;
|
|
18
|
+
return w && w.id === String(id) ? w.id : false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function read(ctx, wsParam) {
|
|
22
|
+
const ws = wsOf(ctx, wsParam);
|
|
23
|
+
if (ws === false) return { status: 404, body: { ok: false, error: 'This workspace is not registered.' } };
|
|
24
|
+
return { status: 200, body: { ok: true, ws: ws || null, ...store.all(ctx.runtimeDir, ws, { env: ctx.env || process.env }), state_dir: ctx.runtimeDir } };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function write(ctx, req, body) {
|
|
28
|
+
const g = guard.requirePagePress(req, body);
|
|
29
|
+
if (!g.ok) return { status: g.status, body: { ok: false, error: g.error } };
|
|
30
|
+
const key = typeof body.key === 'string' ? body.key : '';
|
|
31
|
+
const ws = wsOf(ctx, body.ws);
|
|
32
|
+
if (ws === false) return { status: 404, body: { ok: false, error: 'This workspace is not registered.' } };
|
|
33
|
+
const r = body.reset === true ? store.reset(ctx.runtimeDir, key, ws) : store.set(ctx.runtimeDir, key, body.value, ws);
|
|
34
|
+
if (!r.ok) return { status: r.status || 400, body: { ok: false, key, error: r.error } };
|
|
35
|
+
return { status: 200, body: { ok: true, key, value: r.value } };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
prefixes: ['/api/settings'],
|
|
40
|
+
read, write,
|
|
41
|
+
handle: async (req, res, url, ctx) => {
|
|
42
|
+
if (url.pathname !== '/api/settings') return false;
|
|
43
|
+
if (req.method === 'GET') { const r = read(ctx, url.searchParams.get('ws')); ctx.json(res, r.body, r.status); return true; }
|
|
44
|
+
if (req.method !== 'POST') { ctx.json(res, { ok: false, error: '/api/settings takes GET or POST' }, 405); return true; }
|
|
45
|
+
const got = await ctx.readJson(req);
|
|
46
|
+
if (!got.ok) { ctx.json(res, { ok: false, error: 'invalid JSON body' }, 400); return true; }
|
|
47
|
+
const r = write(ctx, req, got.body || {});
|
|
48
|
+
ctx.json(res, r.body, r.status);
|
|
49
|
+
return true;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
* File: <runtime>/signals/signals.jsonl (append only, scripts/trust/ledger.js). */
|
|
10
10
|
const path = require('path');
|
|
11
11
|
const guard = require('./pm-guard.js');
|
|
12
|
-
const
|
|
13
|
-
const
|
|
12
|
+
const { repoScript } = require('./repo-script.js');
|
|
13
|
+
const S = require(repoScript('signal-rules.js'));
|
|
14
|
+
const L = require(repoScript('trust/ledger.js'));
|
|
14
15
|
|
|
15
16
|
const fileOf = (runtimeDir) => path.join(runtimeDir, 'signals', 'signals.jsonl');
|
|
16
17
|
const fail = (status, error) => ({ status, body: { ok: false, error } });
|