@wenrwa/marketplace-cli 0.5.23 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/__tests__/auth-api-key-format.test.d.ts +1 -0
  2. package/dist/__tests__/auth-api-key-format.test.js +41 -0
  3. package/dist/__tests__/auth-identity.test.d.ts +1 -0
  4. package/dist/__tests__/auth-identity.test.js +46 -0
  5. package/dist/__tests__/auth-login-core.test.d.ts +1 -0
  6. package/dist/__tests__/auth-login-core.test.js +145 -0
  7. package/dist/__tests__/auth.test.js +246 -57
  8. package/dist/__tests__/bridge-bundle-installer.test.js +101 -0
  9. package/dist/__tests__/bridge-context-writer.test.js +168 -1
  10. package/dist/__tests__/bridge-local-context-writer.test.js +51 -2
  11. package/dist/__tests__/bridge-memory-brief.test.d.ts +1 -0
  12. package/dist/__tests__/bridge-memory-brief.test.js +107 -0
  13. package/dist/__tests__/bridge-memory-pointer.test.d.ts +1 -0
  14. package/dist/__tests__/bridge-memory-pointer.test.js +92 -0
  15. package/dist/__tests__/client.test.js +26 -4
  16. package/dist/__tests__/config.test.js +36 -4
  17. package/dist/__tests__/openBrowser.test.d.ts +1 -0
  18. package/dist/__tests__/openBrowser.test.js +42 -0
  19. package/dist/__tests__/prompt.test.d.ts +1 -0
  20. package/dist/__tests__/prompt.test.js +49 -0
  21. package/dist/auth/identity.d.ts +22 -0
  22. package/dist/auth/identity.js +20 -0
  23. package/dist/auth/login.d.ts +80 -0
  24. package/dist/auth/login.js +61 -0
  25. package/dist/bridge/apiClient.d.ts +25 -0
  26. package/dist/bridge/apiClient.js +7 -1
  27. package/dist/bridge/bundleInstaller.d.ts +14 -0
  28. package/dist/bridge/bundleInstaller.js +56 -2
  29. package/dist/bridge/contextWriter.d.ts +24 -1
  30. package/dist/bridge/contextWriter.js +61 -5
  31. package/dist/bridge/localContextWriter.d.ts +13 -1
  32. package/dist/bridge/localContextWriter.js +31 -4
  33. package/dist/bridge/memoryBrief.d.ts +41 -0
  34. package/dist/bridge/memoryBrief.js +63 -0
  35. package/dist/bridge/memoryPointer.d.ts +49 -0
  36. package/dist/bridge/memoryPointer.js +62 -0
  37. package/dist/bridge/pairing.js +7 -1
  38. package/dist/bridge/relayClient.d.ts +20 -1
  39. package/dist/bridge/relayClient.js +45 -6
  40. package/dist/bridge/server.d.ts +10 -1
  41. package/dist/bridge/server.js +29 -10
  42. package/dist/commands/auth.d.ts +17 -0
  43. package/dist/commands/auth.js +202 -27
  44. package/dist/commands/bridge.js +82 -2
  45. package/dist/utils/apiKeyFormat.d.ts +9 -0
  46. package/dist/utils/apiKeyFormat.js +26 -0
  47. package/dist/utils/client.js +7 -1
  48. package/dist/utils/config.d.ts +3 -0
  49. package/dist/utils/config.js +28 -2
  50. package/dist/utils/openBrowser.d.ts +16 -0
  51. package/dist/utils/openBrowser.js +30 -0
  52. package/dist/utils/prompt.d.ts +9 -0
  53. package/dist/utils/prompt.js +63 -0
  54. package/package.json +2 -2
@@ -26,6 +26,18 @@ export interface LocalContextWriteResult {
26
26
  */
27
27
  export declare function writeLocalContext(input: {
28
28
  projectRoot: string;
29
- content: string;
29
+ /** Absent only for a pointers-only refresh (wmr-07). */
30
+ content?: string;
30
31
  workGraphAvailable?: boolean;
32
+ /** wmr-04 — two-lane parity with contextWriter.ts. Gated by the caller on
33
+ * the local lane's own honest signal (did THIS bridge run register the
34
+ * MCP server), not on a container env var that does not exist here. */
35
+ memoryAvailable?: boolean;
36
+ /** wmr-04 — this workspace's composed brief, or null when the fetch failed
37
+ * or there is nothing yet. Never blocks the write (PRD D2). */
38
+ memoryBrief?: string | null;
39
+ /** wmr-07 — refresh only the standing pointers, keeping the pushed file
40
+ * content. The local twin of contextWriter's flag; same reason (the
41
+ * manifest frame only arrives when the pilot has files selected). */
42
+ pointersOnly?: boolean;
31
43
  }, deps: LocalContextWriterDeps): LocalContextWriteResult;
@@ -55,6 +55,16 @@ exports.writeLocalContext = writeLocalContext;
55
55
  */
56
56
  const path = __importStar(require("path"));
57
57
  const workGraphPointer_1 = require("./workGraphPointer");
58
+ const memoryPointer_1 = require("./memoryPointer");
59
+ const contextWriter_1 = require("./contextWriter");
60
+ /** Split a bridge-owned context file into its pointer block and the rest. */
61
+ function splitPointers(body) {
62
+ const start = body.indexOf(contextWriter_1.POINTERS_START_MARKER);
63
+ const end = body.indexOf(contextWriter_1.POINTERS_END_MARKER);
64
+ if (start === -1 || end === -1 || end < start)
65
+ return { rest: body.trim() };
66
+ return { rest: (body.slice(0, start) + body.slice(end + contextWriter_1.POINTERS_END_MARKER.length)).trim() };
67
+ }
58
68
  exports.LOCAL_CONTEXT_DIR = '.hiveop';
59
69
  exports.LOCAL_CONTEXT_FILENAME = 'context.md';
60
70
  exports.LOCAL_CONTEXT_RELATIVE_PATH = `${exports.LOCAL_CONTEXT_DIR}/${exports.LOCAL_CONTEXT_FILENAME}`;
@@ -72,10 +82,27 @@ function writeLocalContext(input, deps) {
72
82
  // to pre-wgcp-01 behavior (never pointing at unregistered MCP tools); when
73
83
  // present, the pointer alone is reason enough to write the file, even with
74
84
  // no pushed manifest this turn.
75
- const hasFileContent = input.content.trim().length > 0;
76
- const content = input.workGraphAvailable
77
- ? (hasFileContent ? `${workGraphPointer_1.WORK_GRAPH_POINTER}\n\n${input.content}` : workGraphPointer_1.WORK_GRAPH_POINTER)
78
- : input.content;
85
+ //
86
+ // wmr-04 adds the memory section under the same rule, assembled as a list of
87
+ // present sections so the joining and empty-content logic is stated once —
88
+ // the same shape contextWriter.ts uses, because the two lanes diverging on
89
+ // this would be a two-lane bug nobody would notice until a local pilot
90
+ // reported missing memory.
91
+ const pointers = [];
92
+ if (input.workGraphAvailable)
93
+ pointers.push(workGraphPointer_1.WORK_GRAPH_POINTER);
94
+ if (input.memoryAvailable)
95
+ pointers.push((0, memoryPointer_1.memorySection)(input.memoryBrief));
96
+ // wmr-07 — the whole local file is bridge-owned, so "preserve the pushed
97
+ // content" means keeping what is already on disk below the pointer fence.
98
+ const existingBody = existed ? deps.readFile(contextPath) : '';
99
+ const fileContent = input.pointersOnly
100
+ ? splitPointers(existingBody).rest
101
+ : (input.content ?? '').trim();
102
+ const block = pointers.length > 0
103
+ ? `${contextWriter_1.POINTERS_START_MARKER}\n${pointers.join('\n\n')}\n${contextWriter_1.POINTERS_END_MARKER}`
104
+ : '';
105
+ const content = [block, fileContent].filter((part) => part.length > 0).join('\n\n');
79
106
  const isEmpty = content.trim().length === 0;
80
107
  if (isEmpty) {
81
108
  if (!existed)
@@ -0,0 +1,41 @@
1
+ /**
2
+ * wmr-02 — bridge-side fetcher for the workspace memory brief
3
+ * (`GET /workspaces/:id/memory/brief`, PRD decision D2 —
4
+ * docs/prd/workspace-memory-read-path.md). Mirrors storageQuotaFetch.ts's
5
+ * shape: injectable fetchImpl (defaults to the global fetch every Node
6
+ * >=18 the CLI already requires ships), ANY failure resolves null, never
7
+ * throws. This is deliberately a separate slice from anything that
8
+ * consumes the brief — proving the failure behaviour before depending on
9
+ * it is the whole point (see the PRD).
10
+ *
11
+ * Its entire contract: never throws, never hangs. On a non-200, network
12
+ * error, malformed/missing body, a missing/empty Connect Token, or a
13
+ * timeout, it resolves null so the caller writes the context region
14
+ * exactly as it would have anyway — memory can never become a single
15
+ * point of failure for opening a terminal.
16
+ */
17
+ export type FetchLike = (url: string, init?: {
18
+ method?: string;
19
+ headers?: Record<string, string>;
20
+ signal?: AbortSignal;
21
+ }) => Promise<{
22
+ ok: boolean;
23
+ status: number;
24
+ json: () => Promise<unknown>;
25
+ }>;
26
+ export interface FetchMemoryBriefOpts {
27
+ /** Injectable transport — tests stub this so nothing hits the network,
28
+ * same discipline as apiClient.ts / storageQuotaFetch.ts. Defaults to
29
+ * the global fetch. */
30
+ fetchImpl?: FetchLike;
31
+ /** Overrides the default bounded timeout, in ms. Tests use this to keep
32
+ * the timeout case fast without waiting out the real default. */
33
+ timeoutMs?: number;
34
+ }
35
+ /**
36
+ * Fetch the workspace memory brief for `workspaceId`. See the module
37
+ * header for the full failure contract — every branch below returns null
38
+ * rather than propagating an error or letting a slow response block the
39
+ * caller.
40
+ */
41
+ export declare function fetchMemoryBrief(workspaceId: string, opts?: FetchMemoryBriefOpts): Promise<string | null>;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ /**
3
+ * wmr-02 — bridge-side fetcher for the workspace memory brief
4
+ * (`GET /workspaces/:id/memory/brief`, PRD decision D2 —
5
+ * docs/prd/workspace-memory-read-path.md). Mirrors storageQuotaFetch.ts's
6
+ * shape: injectable fetchImpl (defaults to the global fetch every Node
7
+ * >=18 the CLI already requires ships), ANY failure resolves null, never
8
+ * throws. This is deliberately a separate slice from anything that
9
+ * consumes the brief — proving the failure behaviour before depending on
10
+ * it is the whole point (see the PRD).
11
+ *
12
+ * Its entire contract: never throws, never hangs. On a non-200, network
13
+ * error, malformed/missing body, a missing/empty Connect Token, or a
14
+ * timeout, it resolves null so the caller writes the context region
15
+ * exactly as it would have anyway — memory can never become a single
16
+ * point of failure for opening a terminal.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.fetchMemoryBrief = fetchMemoryBrief;
20
+ const DEFAULT_API_URL = 'https://api.wenrwa.com/api/v1';
21
+ /** Bounded so a hanging backend can never stall a terminal open. */
22
+ const DEFAULT_TIMEOUT_MS = 4000;
23
+ /**
24
+ * Fetch the workspace memory brief for `workspaceId`. See the module
25
+ * header for the full failure contract — every branch below returns null
26
+ * rather than propagating an error or letting a slow response block the
27
+ * caller.
28
+ */
29
+ async function fetchMemoryBrief(workspaceId, opts = {}) {
30
+ const token = process.env.WENRWA_CONNECT_TOKEN;
31
+ if (!token)
32
+ return null; // missing/empty token — never even attempt the call
33
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
34
+ if (!fetchImpl)
35
+ return null;
36
+ const apiUrl = process.env.MARKETPLACE_API_URL || DEFAULT_API_URL;
37
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
38
+ const url = `${apiUrl}/workspaces/${encodeURIComponent(workspaceId)}/memory/brief`;
39
+ const controller = new AbortController();
40
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
41
+ try {
42
+ const res = await fetchImpl(url, {
43
+ method: 'GET',
44
+ headers: { Authorization: `Bearer ${token}` },
45
+ signal: controller.signal,
46
+ });
47
+ if (!res.ok)
48
+ return null;
49
+ const body = (await res.json());
50
+ if (body?.success !== true || typeof body.brief !== 'string' || body.brief.length === 0) {
51
+ return null; // malformed/missing body — never a fabricated brief
52
+ }
53
+ return body.brief;
54
+ }
55
+ catch {
56
+ // network error, abort/timeout, or json() throwing on malformed JSON —
57
+ // all collapse to the same UNKNOWN outcome, never thrown to the caller.
58
+ return null;
59
+ }
60
+ finally {
61
+ clearTimeout(timer);
62
+ }
63
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * wmr-01 — the guidance that tells an agent workspace memory exists at all.
3
+ *
4
+ * Workspace Memory (ADR-0044) shipped complete — store, verdicts, curation UI,
5
+ * MCP tools — and held ZERO entries in production for a month. Two reasons, and
6
+ * only one of them was a bug: agents were structurally forbidden from writing
7
+ * (a Connect Token allowlist omission, fixed in cb5e725), and nothing ever told
8
+ * them the store existed. Fixing the first without the second just swaps a 403
9
+ * for an agent that never calls the tool.
10
+ *
11
+ * Delivered through the SAME managed-region mechanism as the work-graph pointer
12
+ * (wgcp-01, workGraphPointer.ts) — contextWriter.ts for the hosted lane,
13
+ * localContextWriter.ts for local. No new delivery channel, and no image
14
+ * rebuild: `--append-system-prompt` does not exist in this codebase, and the
15
+ * hosted lane launches `claude` from a script baked into the image, so argv is
16
+ * not reachable from here.
17
+ *
18
+ * Gated by the CALLER on `WENRWA_CONNECT_TOKEN`, exactly as the work-graph
19
+ * pointer is: absent means the MCP tools this text names are not registered in
20
+ * this process, and pointing an agent at a tool it does not have is worse than
21
+ * silence.
22
+ *
23
+ * WHAT THIS TEXT HAS TO EARN. It rides in every agent's context on every
24
+ * attach, so it states a floor as well as a prompt: without one, the cheapest
25
+ * thing for a model to record is whatever it just read, which is exactly what
26
+ * the code already says and what nobody needs repeated. It also never tells an
27
+ * agent that memory is settled truth — entries carry verdicts, the brief
28
+ * renders its own `⚠ re-verify before relying:` flags, and the predecessor
29
+ * this epic replaced (lib/agent-memory.ts) failed precisely by prompting
30
+ * "confirmed facts — do not re-verify".
31
+ */
32
+ export declare const MEMORY_POINTER: string;
33
+ /**
34
+ * wmr-03 — the memory section as it appears in the managed region: the
35
+ * standing instruction, followed by this workspace's brief when there is one.
36
+ *
37
+ * THE BRIEF IS CARRIED VERBATIM. It arrives already composed and already
38
+ * capped by the backend (brief.service.ts: 2048 bytes, tiered pinned →
39
+ * STILL_VALID/ASSERTED → DRIFTED → UNVERIFIED, each rendered with its own flag
40
+ * text). Re-ranking, re-wrapping or re-capping it here would give the agent a
41
+ * different view of the same workspace than the Memory panel gives the human,
42
+ * and the two would drift apart with no test able to see it. The one judgement
43
+ * this function makes is whether there is anything to show at all.
44
+ *
45
+ * `null` — the fetch failed, or the workspace has no memory yet — renders the
46
+ * instruction alone rather than an empty heading. An empty heading reads as
47
+ * "this workspace knows nothing", which is a different and wrong claim.
48
+ */
49
+ export declare function memorySection(brief: string | null | undefined): string;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MEMORY_POINTER = void 0;
4
+ exports.memorySection = memorySection;
5
+ /**
6
+ * wmr-01 — the guidance that tells an agent workspace memory exists at all.
7
+ *
8
+ * Workspace Memory (ADR-0044) shipped complete — store, verdicts, curation UI,
9
+ * MCP tools — and held ZERO entries in production for a month. Two reasons, and
10
+ * only one of them was a bug: agents were structurally forbidden from writing
11
+ * (a Connect Token allowlist omission, fixed in cb5e725), and nothing ever told
12
+ * them the store existed. Fixing the first without the second just swaps a 403
13
+ * for an agent that never calls the tool.
14
+ *
15
+ * Delivered through the SAME managed-region mechanism as the work-graph pointer
16
+ * (wgcp-01, workGraphPointer.ts) — contextWriter.ts for the hosted lane,
17
+ * localContextWriter.ts for local. No new delivery channel, and no image
18
+ * rebuild: `--append-system-prompt` does not exist in this codebase, and the
19
+ * hosted lane launches `claude` from a script baked into the image, so argv is
20
+ * not reachable from here.
21
+ *
22
+ * Gated by the CALLER on `WENRWA_CONNECT_TOKEN`, exactly as the work-graph
23
+ * pointer is: absent means the MCP tools this text names are not registered in
24
+ * this process, and pointing an agent at a tool it does not have is worse than
25
+ * silence.
26
+ *
27
+ * WHAT THIS TEXT HAS TO EARN. It rides in every agent's context on every
28
+ * attach, so it states a floor as well as a prompt: without one, the cheapest
29
+ * thing for a model to record is whatever it just read, which is exactly what
30
+ * the code already says and what nobody needs repeated. It also never tells an
31
+ * agent that memory is settled truth — entries carry verdicts, the brief
32
+ * renders its own `⚠ re-verify before relying:` flags, and the predecessor
33
+ * this epic replaced (lib/agent-memory.ts) failed precisely by prompting
34
+ * "confirmed facts — do not re-verify".
35
+ */
36
+ exports.MEMORY_POINTER = [
37
+ '## Workspace memory',
38
+ 'This workspace has a shared memory other agents read: `memory_write` to record something, `memory_recall` to search it.',
39
+ 'Worth recording: a correction you were given, a decision and the reason behind it, a non-obvious fact and the file you learned it from.',
40
+ 'Not worth recording: anything the code already states, or anything you merely read this session.',
41
+ ].join('\n');
42
+ /**
43
+ * wmr-03 — the memory section as it appears in the managed region: the
44
+ * standing instruction, followed by this workspace's brief when there is one.
45
+ *
46
+ * THE BRIEF IS CARRIED VERBATIM. It arrives already composed and already
47
+ * capped by the backend (brief.service.ts: 2048 bytes, tiered pinned →
48
+ * STILL_VALID/ASSERTED → DRIFTED → UNVERIFIED, each rendered with its own flag
49
+ * text). Re-ranking, re-wrapping or re-capping it here would give the agent a
50
+ * different view of the same workspace than the Memory panel gives the human,
51
+ * and the two would drift apart with no test able to see it. The one judgement
52
+ * this function makes is whether there is anything to show at all.
53
+ *
54
+ * `null` — the fetch failed, or the workspace has no memory yet — renders the
55
+ * instruction alone rather than an empty heading. An empty heading reads as
56
+ * "this workspace knows nothing", which is a different and wrong claim.
57
+ */
58
+ function memorySection(brief) {
59
+ if (!brief || brief.trim().length === 0)
60
+ return exports.MEMORY_POINTER;
61
+ return `${exports.MEMORY_POINTER}\n\n${brief.trim()}`;
62
+ }
@@ -18,7 +18,13 @@ async function runPairingPoll(deps) {
18
18
  const result = await deps.poll();
19
19
  if (deps.isCancelled())
20
20
  return { outcome: 'cancelled' };
21
- if (result.status === 'approved') {
21
+ // wha-08: PairingPollResponse's 'approved' status now also covers
22
+ // cli_login (auth/login.ts), which carries an apiKey instead of a
23
+ // connectToken/deviceId — `purpose !== 'cli_login'` is a type-only
24
+ // narrowing, never a behavior change: a bridge pollToken never yields a
25
+ // cli_login-shaped response, so this condition is always true for any
26
+ // real bridge poll.
27
+ if (result.status === 'approved' && result.purpose !== 'cli_login') {
22
28
  return { outcome: 'approved', connectToken: result.connectToken, deviceId: result.deviceId };
23
29
  }
24
30
  if (result.status === 'expired') {
@@ -221,13 +221,32 @@ export interface RelayClientOpts {
221
221
  * error (legacy / local bridge has no workspace checkouts). This opens NO PTY
222
222
  * and does NOT feed Claude Code via keystrokes — the context arrives as a FILE.
223
223
  */
224
+ /**
225
+ * wmr-07 — refresh this workspace's standing pointers (work graph, memory
226
+ * brief) without touching its pushed file content. Called when a session
227
+ * opens, because the context-manifest frame only arrives when the pilot has
228
+ * files selected. Absent → nothing happens, exactly as before.
229
+ */
230
+ refreshContextPointers?: (args: {
231
+ workspaceId: string;
232
+ }) => void | Promise<void>;
224
233
  writeContext?: (manifest: {
225
234
  workspaceId: string;
226
235
  content: string;
227
236
  }) => {
228
237
  action: string;
229
238
  claudeMdPath: string | null;
230
- } | void;
239
+ } | void
240
+ /**
241
+ * wmr-03 — may be async: the hosted implementation fetches this
242
+ * workspace's memory brief before writing the region. The frame handler
243
+ * below resolves it rather than becoming async itself, so no other frame
244
+ * type's ordering changes.
245
+ */
246
+ | Promise<{
247
+ action: string;
248
+ claudeMdPath: string | null;
249
+ } | void>;
231
250
  /**
232
251
  * cve-06 (ADR-0047 / ADR-0032): read ONE file out of the container's
233
252
  * workspace checkout — the hosted-lane counterpart of the local bridge's
@@ -1009,6 +1009,31 @@ function startRelayClient(opts) {
1009
1009
  relayOwned.set(sessionId, { workspaceId });
1010
1010
  // tchp-21 — best-effort, fire-and-forget: never blocks `opened`.
1011
1011
  maybeCreateSessionAnchor(sessionId, cwd);
1012
+ /*
1013
+ wmr-07 — refresh the standing pointers (work graph, workspace
1014
+ memory + its brief) for this workspace now that a session
1015
+ exists.
1016
+
1017
+ This is the trigger the whole delivery was missing. wmr-01..04
1018
+ rode the context-manifest frame on the assumption it fires when
1019
+ a terminal attaches; it does not — arena only pushes one when
1020
+ the pilot has FILES SELECTED, so for an ordinary workspace the
1021
+ region was never written and neither the memory brief nor the
1022
+ work-graph pointer (shipped months earlier) ever arrived.
1023
+ Confirmed on a live container: CLAUDE.md untouched for 20 hours
1024
+ with neither present.
1025
+
1026
+ Doing it here needs no browser, no selection, and no arena at
1027
+ all — only a session opening, which is exactly when an agent is
1028
+ about to read the file. Pointers-only, so the pilot's pushed
1029
+ file content is preserved. Fire-and-forget: this must never
1030
+ delay or fail an `opened`.
1031
+ */
1032
+ if (workspaceId && opts.refreshContextPointers) {
1033
+ void Promise.resolve(opts.refreshContextPointers({ workspaceId })).catch((err) => {
1034
+ console.error('[bridge] context pointer refresh failed (non-fatal):', err);
1035
+ });
1036
+ }
1012
1037
  attachTo(sessionId)
1013
1038
  .then(() => {
1014
1039
  send(socket, {
@@ -1274,12 +1299,26 @@ function startRelayClient(opts) {
1274
1299
  send(socket, { t: 'error', message: 'context-manifest unsupported (no workspace checkouts)' });
1275
1300
  break;
1276
1301
  }
1277
- const result = opts.writeContext({ workspaceId: frame.workspaceId, content: frame.content ?? '' });
1278
- send(socket, {
1279
- t: 'context-written',
1280
- workspaceId: frame.workspaceId,
1281
- ...(frame.sessionId ? { sessionId: frame.sessionId } : {}),
1282
- ...(result && typeof result === 'object' ? { action: result.action } : {}),
1302
+ // wmr-03: the implementation may be async (it fetches the memory
1303
+ // brief). Resolved here so the outer message handler stays sync —
1304
+ // making IT async would change ordering for every frame type. A
1305
+ // rejection can only come from a bug in the writer, never from the
1306
+ // brief fetch (which resolves null on any failure, PRD D2), so it
1307
+ // is answered rather than thrown into an unhandled rejection.
1308
+ Promise.resolve(opts.writeContext({ workspaceId: frame.workspaceId, content: frame.content ?? '' }))
1309
+ .then((result) => {
1310
+ send(socket, {
1311
+ t: 'context-written',
1312
+ workspaceId: frame.workspaceId,
1313
+ ...(frame.sessionId ? { sessionId: frame.sessionId } : {}),
1314
+ ...(result && typeof result === 'object' ? { action: result.action } : {}),
1315
+ });
1316
+ })
1317
+ .catch((err) => {
1318
+ send(socket, {
1319
+ t: 'error',
1320
+ message: `context-manifest failed: ${err instanceof Error ? err.message : String(err)}`,
1321
+ });
1283
1322
  });
1284
1323
  break;
1285
1324
  }
@@ -197,10 +197,19 @@ export interface BridgeServerOpts {
197
197
  * this is a FILE write, not PTY input (ADR-0028), mirroring the hosted
198
198
  * lane's contextWriter.ts/relayClient.ts contract.
199
199
  */
200
+ /** wmr-04 — may be async: the implementation fetches this workspace's memory
201
+ * brief before writing. The frame handler resolves it rather than becoming
202
+ * async itself, so no other frame type's ordering changes. */
203
+ /** wmr-07 — the local twin of the hosted refreshContextPointers: write this
204
+ * workspace's standing pointers when a session opens, without waiting for a
205
+ * context-manifest that only arrives when files are selected. */
206
+ refreshLocalContextPointers?: (args: {
207
+ workspaceId: string;
208
+ }) => void | Promise<void>;
200
209
  writeLocalContext?: (params: {
201
210
  workspaceId: string;
202
211
  content: string;
203
- }) => LocalContextWriteResult | null;
212
+ }) => LocalContextWriteResult | null | Promise<LocalContextWriteResult | null>;
204
213
  /**
205
214
  * fcct-24 — path to the local HiveOp activity JSONL file the activity
206
215
  * hooks (registered via a directly-spawned agent's `--settings`, see
@@ -766,6 +766,14 @@ function startBridgeServer(opts) {
766
766
  send(ws, { t: 'error', message });
767
767
  break;
768
768
  }
769
+ // wmr-07 — same trigger as the hosted lane: the pointers are
770
+ // refreshed when a session opens, not when a manifest happens to
771
+ // be pushed. Fire-and-forget; never delays or fails `opened`.
772
+ if (frame.workspaceId && opts.refreshLocalContextPointers) {
773
+ void Promise.resolve(opts.refreshLocalContextPointers({ workspaceId: frame.workspaceId })).catch((err) => {
774
+ console.error('[bridge] local context pointer refresh failed (non-fatal):', err);
775
+ });
776
+ }
769
777
  attachTo(sessionId).then(() => {
770
778
  send(ws, {
771
779
  t: 'opened',
@@ -874,16 +882,27 @@ function startBridgeServer(opts) {
874
882
  send(ws, { t: 'error', message: 'context-manifest unsupported (no local writer wired)' });
875
883
  break;
876
884
  }
877
- const result = opts.writeLocalContext({ workspaceId: frame.workspaceId, content: frame.content });
878
- if (!result) {
879
- send(ws, { t: 'error', message: 'unknown local workspace' });
880
- break;
881
- }
882
- send(ws, {
883
- t: 'context-written',
884
- workspaceId: frame.workspaceId,
885
- ...(frame.sessionId ? { sessionId: frame.sessionId } : {}),
886
- action: result.action,
885
+ // wmr-04: mirrors the hosted lane — resolved here so the outer
886
+ // handler stays sync. A rejection can only be a bug in the writer;
887
+ // the brief fetch resolves null on any failure (PRD D2).
888
+ Promise.resolve(opts.writeLocalContext({ workspaceId: frame.workspaceId, content: frame.content }))
889
+ .then((result) => {
890
+ if (!result) {
891
+ send(ws, { t: 'error', message: 'unknown local workspace' });
892
+ return;
893
+ }
894
+ send(ws, {
895
+ t: 'context-written',
896
+ workspaceId: frame.workspaceId,
897
+ ...(frame.sessionId ? { sessionId: frame.sessionId } : {}),
898
+ action: result.action,
899
+ });
900
+ })
901
+ .catch((err) => {
902
+ send(ws, {
903
+ t: 'error',
904
+ message: `context-manifest failed: ${err instanceof Error ? err.message : String(err)}`,
905
+ });
887
906
  });
888
907
  break;
889
908
  }
@@ -1,2 +1,19 @@
1
+ /**
2
+ * wha-08 (docs/prd/retire-wallet-header-auth.md, docs/CONTEXT-identity.md) —
3
+ * `wen-market auth login` / `auth logout` / `auth whoami`.
4
+ *
5
+ * `login` runs the device-code flow (lws-14's mechanism, purpose=cli_login —
6
+ * see auth/login.ts): a code is minted and printed with the approval URL
7
+ * (https://marketplace.wenrwa.com/settings/cli-login?code=<CODE>), the
8
+ * browser is opened as best-effort convenience, and the CLI polls until a
9
+ * human approves or denies it there. `--api-key` skips all of that for CI:
10
+ * an existing key is verified with one authenticated call and saved
11
+ * directly. Either way the CLI authenticates as an API key (X-API-Key) —
12
+ * never the retired X-Wallet-Pubkey header (see utils/client.ts).
13
+ *
14
+ * `logout` revokes the stored key server-side (DELETE /keys/:id — see
15
+ * ApiKeyController#revoke's wha-08 self-revoke-only guard) before deleting
16
+ * it locally, so a copied config file stops working too.
17
+ */
1
18
  import { Command } from 'commander';
2
19
  export declare const authCommand: Command;