create-pmos 0.24.0 → 0.26.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/README.md +27 -0
- package/package.json +1 -1
- package/payload/KIT-VERSION +1 -1
- package/workspace/README.md +16 -11
- package/workspace/lib/four-panel.js +201 -6
- package/workspace/lib/working-set.js +124 -28
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/data.js +95 -15
- package/workspace/public/pm/panel-detail.js +501 -95
- package/workspace/public/pm/panel-initiatives.js +124 -16
- package/workspace/public/pm/state.js +11 -0
- package/workspace/public/pm/tab-term.js +13 -1
- package/workspace/server.js +169 -6
|
@@ -38,11 +38,17 @@
|
|
|
38
38
|
*
|
|
39
39
|
* ── The one thing this file refuses to do ───────────────────────────────────────────────────
|
|
40
40
|
* Linking an artifact records a DECLARATION (`{artifact_path, kind, target}` appended to
|
|
41
|
-
* `artifact-links.jsonl`)
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* `artifact-links.jsonl`) and choosing a sync direction records another (`artifact-sync.jsonl`).
|
|
42
|
+
* Neither makes an outbound request, because PMOS holds no credential: the request is made by an
|
|
43
|
+
* agent session holding the PM's own MCP, and what comes back here is a report (D53).
|
|
44
|
+
*
|
|
45
|
+
* `state.connectors` used to be published as a literal `[]` on every load. That was written when it
|
|
46
|
+
* was true of every machine, and it stayed after the server learned to DERIVE the answer from the
|
|
47
|
+
* PM's agent profiles — so a careful derivation shipped into a client that had already decided. The
|
|
48
|
+
* panel branches on it (`hasConnector`, `resolverFor`), so the visible cost was a control that never
|
|
49
|
+
* rendered and a machine with a Jira MCP that reported having none. It is now published from the
|
|
50
|
+
* payload, and the constant is gone rather than corrected: a constant cannot be wrong, which is
|
|
51
|
+
* exactly why it could not be trusted.
|
|
46
52
|
*/
|
|
47
53
|
|
|
48
54
|
import { state, setState, subscribe, FIXTURE } from './state.js';
|
|
@@ -299,20 +305,36 @@ function linksFor(payload) {
|
|
|
299
305
|
out.push({ kind: l.kind, label: l.declared, path: l.href || l.declared, st: l.state_text || l.state });
|
|
300
306
|
}
|
|
301
307
|
for (const r of (payload.declarations && payload.declarations.links) || []) {
|
|
302
|
-
|
|
308
|
+
// The row's OWN provenance, carried rather than replaced. `declared · not connected` was a
|
|
309
|
+
// literal written before the store had a state vocabulary, and it outlived it: a reference a
|
|
310
|
+
// session had genuinely resolved rendered the same seven words as one the PM had typed a second
|
|
311
|
+
// earlier. The panel derives its state text from these fields (`linkStateText`), so carrying
|
|
312
|
+
// them is the whole fix — there is no second opinion to keep in step.
|
|
313
|
+
out.push({
|
|
314
|
+
kind: r.kind, label: r.label, path: r.target,
|
|
315
|
+
state: r.state, origin: r.origin, profile: r.profile || null,
|
|
316
|
+
reason: r.reason || null, resolved_at: r.resolved_at || null,
|
|
317
|
+
});
|
|
303
318
|
}
|
|
304
319
|
return out;
|
|
305
320
|
}
|
|
306
321
|
|
|
307
|
-
/** Artifacts, with any declared external destination attached. */
|
|
322
|
+
/** Artifacts, with any declared external destination and the latest sync for it attached. */
|
|
308
323
|
function artsFor(payload) {
|
|
309
324
|
const dest = new Map();
|
|
310
325
|
for (const r of (payload.declarations && payload.declarations.artifacts) || []) {
|
|
311
|
-
|
|
326
|
+
// The server has already collapsed the store to one row per pair and dropped what the PM
|
|
327
|
+
// retracted, so what arrives here is the statement of now.
|
|
328
|
+
dest.set(r.artifact_path, { kind: r.kind, target: r.target, state: r.state, origin: r.origin });
|
|
329
|
+
}
|
|
330
|
+
const sync = new Map();
|
|
331
|
+
for (const r of (payload.declarations && payload.declarations.sync) || []) {
|
|
332
|
+
sync.set(r.artifact_path + '|' + r.kind, r);
|
|
312
333
|
}
|
|
313
334
|
return (payload.artifacts || []).map((a) => {
|
|
314
335
|
const d = dest.get(a.path);
|
|
315
|
-
|
|
336
|
+
if (!d) return { role: a.role, path: a.path };
|
|
337
|
+
return { role: a.role, path: a.path, dest: d, sync: sync.get(a.path + '|' + d.kind) || null };
|
|
316
338
|
});
|
|
317
339
|
}
|
|
318
340
|
|
|
@@ -662,7 +684,14 @@ async function hydrate(slug) {
|
|
|
662
684
|
// and its footer ("read failed · as of …") is entitled to say when it was attempted.
|
|
663
685
|
const asOf = readStamp();
|
|
664
686
|
hydrated[slug] = { ...recordFrom(payload), asOf };
|
|
665
|
-
setState((s) => ({
|
|
687
|
+
setState((s) => ({
|
|
688
|
+
records: { ...s.records, [slug]: hydrated[slug] },
|
|
689
|
+
// The server's `{configured:[{kind, profile, server, declared}], declaredBy, note}`, straight
|
|
690
|
+
// through. Published on every hydration rather than once at boot, because a profile added
|
|
691
|
+
// while the workspace is open should reach the panel on the next selection rather than on the
|
|
692
|
+
// next restart — and because the payload is the only place this fact is ever produced.
|
|
693
|
+
connectors: payload && payload.connectors ? payload.connectors : s.connectors,
|
|
694
|
+
}));
|
|
666
695
|
return payload;
|
|
667
696
|
} finally { inFlight.delete(slug); }
|
|
668
697
|
}
|
|
@@ -780,7 +809,9 @@ export async function loadRepos() {
|
|
|
780
809
|
const sel = state.sel || (p ? firstSlugOf(p.name) : null);
|
|
781
810
|
setState({
|
|
782
811
|
wsId: p ? p.ws : null,
|
|
783
|
-
|
|
812
|
+
// NOT initialised to a value here. What is resolvable is the server's derivation from the PM's
|
|
813
|
+
// agent profiles, it arrives with the first `/api/initiative` payload, and seeding a `[]` in the
|
|
814
|
+
// meantime is not a placeholder — it is an answer, and the panel would render it as one.
|
|
784
815
|
product: state.product && products.has(state.product) ? state.product : first,
|
|
785
816
|
sel,
|
|
786
817
|
});
|
|
@@ -926,6 +957,29 @@ export function startSession(slug, sess) {
|
|
|
926
957
|
setTimeout(pollSessions, 800);
|
|
927
958
|
}
|
|
928
959
|
|
|
960
|
+
/**
|
|
961
|
+
* Bind a RUNNING session to an initiative — the PM's own word about work already in flight
|
|
962
|
+
* (workspace-session-attribution R3).
|
|
963
|
+
*
|
|
964
|
+
* Every session that existed before the recorded field shipped has no attribution, and so does every
|
|
965
|
+
* session started outside PMOS. Without this the field would be a promise about future sessions and
|
|
966
|
+
* no help at all with the ones on screen.
|
|
967
|
+
*
|
|
968
|
+
* What comes back is the server's answer, not an assumption: `previous` names the initiative this
|
|
969
|
+
* replaced, so a re-bind the PM did not mean to make is visible rather than silent. Pass
|
|
970
|
+
* `initiative: null` to clear a binding — the honest way back from a wrong one.
|
|
971
|
+
*
|
|
972
|
+
* The poll is scheduled rather than the state patched locally: `/api/sessions` is the one reader of
|
|
973
|
+
* this fact, and writing a second opinion into `state.sessions` here is how the board and the server
|
|
974
|
+
* would come to disagree about what a session is for.
|
|
975
|
+
*/
|
|
976
|
+
export async function bindSessionInitiative(id, initiative) {
|
|
977
|
+
if (FIXTURE) return { ok: false, error: 'fixture mode has no server' };
|
|
978
|
+
const out = await postJson('/api/session/initiative', { id, initiative: initiative || null });
|
|
979
|
+
if (out && out.ok) setTimeout(pollSessions, 200);
|
|
980
|
+
return out || { ok: false, error: 'the workspace server did not answer' };
|
|
981
|
+
}
|
|
982
|
+
|
|
929
983
|
/* ── the scratchpad ─────────────────────────────────────────────────────────────────────────────
|
|
930
984
|
* The pad is PER SESSION, and the server has always stored it that way: `/api/scratchpad` is keyed
|
|
931
985
|
* by the server-issued session id (workspace-scoped, one file per session, deleted with it). What
|
|
@@ -992,7 +1046,7 @@ export async function saveDoc({ ws, path, base, text }) {
|
|
|
992
1046
|
* Appends ONE declaration to `artifact-links.jsonl`. It contacts nothing: PMOS holds no credential,
|
|
993
1047
|
* so this records what the PM says the mapping is and says nothing about the destination.
|
|
994
1048
|
*/
|
|
995
|
-
export async function recordArtifactLink({ artifact_path, slug, kind, target }) {
|
|
1049
|
+
export async function recordArtifactLink({ artifact_path, slug, kind, target, origin, state: st }) {
|
|
996
1050
|
if (FIXTURE) return { ok: true };
|
|
997
1051
|
const p = products.get(currentProductName());
|
|
998
1052
|
const out = await postJson('/api/initiative/link', {
|
|
@@ -1001,11 +1055,37 @@ export async function recordArtifactLink({ artifact_path, slug, kind, target })
|
|
|
1001
1055
|
// The surface labels a Google destination `google doc`; the store's vocabulary is `google`.
|
|
1002
1056
|
kind: String(kind || '').startsWith('google') ? 'google' : kind,
|
|
1003
1057
|
target,
|
|
1058
|
+
// Passed through rather than defaulted here. The server owns the rules about which origin may
|
|
1059
|
+
// claim which state — including that a retraction is the PM's alone — and a client that filled
|
|
1060
|
+
// these in itself would be a second opinion about provenance, which is the one thing this store
|
|
1061
|
+
// must have exactly one of.
|
|
1062
|
+
origin, state: st,
|
|
1004
1063
|
});
|
|
1005
1064
|
if (out && out.ok === false) console.warn('[pm] the artifact link was NOT recorded: ' + out.error);
|
|
1006
1065
|
return out;
|
|
1007
1066
|
}
|
|
1008
1067
|
|
|
1068
|
+
/**
|
|
1069
|
+
* panel-detail.js — `recordSync({artifact_path, slug, kind, target, direction, profile})`.
|
|
1070
|
+
*
|
|
1071
|
+
* Records the direction the PM chose and returns the TASK a session will run to carry it out. It
|
|
1072
|
+
* syncs nothing: the row lands as `proposed`, which is the only state PMOS can produce on its own,
|
|
1073
|
+
* and it stays that way until a session that actually performed the write reports back and names
|
|
1074
|
+
* itself. Nothing here contacts Confluence, Jira or Google, and nothing here can.
|
|
1075
|
+
*/
|
|
1076
|
+
export async function recordSync({ artifact_path, slug, kind, target, direction, profile }) {
|
|
1077
|
+
if (FIXTURE) return { ok: false, error: 'fixture mode records no sync' };
|
|
1078
|
+
const p = products.get(currentProductName());
|
|
1079
|
+
const out = await postJson('/api/initiative/sync', {
|
|
1080
|
+
ws: state.wsId || (p ? p.ws : ''),
|
|
1081
|
+
initiative_id: slug, artifact_path,
|
|
1082
|
+
kind: String(kind || '').startsWith('google') ? 'google' : kind,
|
|
1083
|
+
target, direction, profile: profile || null,
|
|
1084
|
+
});
|
|
1085
|
+
if (out && out.ok === false) console.warn('[pm] the sync direction was NOT recorded: ' + out.error);
|
|
1086
|
+
return out;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1009
1089
|
/**
|
|
1010
1090
|
* panel-detail.js — `lookupTask({kind, subject, hint, profile})`.
|
|
1011
1091
|
*
|
|
@@ -1018,13 +1098,13 @@ export async function lookupTask({ kind, subject, hint, profile }) {
|
|
|
1018
1098
|
return postJson('/api/initiative/lookup-task', { kind, subject, hint, profile });
|
|
1019
1099
|
}
|
|
1020
1100
|
|
|
1021
|
-
/** panel-detail.js — `recordInitiativeLink({slug, kind, label, path,
|
|
1022
|
-
export async function recordInitiativeLink({ slug, kind, label, path }) {
|
|
1101
|
+
/** panel-detail.js — `recordInitiativeLink({slug, kind, label, path, origin, state})` → `initiative-links.jsonl`. */
|
|
1102
|
+
export async function recordInitiativeLink({ slug, kind, label, path, origin, state: st }) {
|
|
1023
1103
|
if (FIXTURE) return { ok: true };
|
|
1024
1104
|
const p = products.get(currentProductName());
|
|
1025
1105
|
const out = await postJson('/api/initiative/link', {
|
|
1026
1106
|
store: 'initiative', ws: state.wsId || (p ? p.ws : ''),
|
|
1027
|
-
initiative_id: slug, kind, label, target: path,
|
|
1107
|
+
initiative_id: slug, kind, label, target: path, origin, state: st,
|
|
1028
1108
|
});
|
|
1029
1109
|
if (out && out.ok === false) console.warn('[pm] the reference link was NOT recorded: ' + out.error);
|
|
1030
1110
|
return out;
|