@rubytech/create-maxy-code 0.1.219 → 0.1.222
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/dist/index.js +46 -1
- package/package.json +1 -1
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +60 -23
- package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.d.ts +4 -1
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +17 -5
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +58 -31
- package/payload/platform/lib/graph-write/src/index.ts +16 -7
- package/payload/platform/plugins/admin/PLUGIN.md +2 -3
- package/payload/platform/plugins/admin/__tests__/admin-can-enumerate.test.sh +129 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +1 -2
- package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +3 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js +41 -9
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/references/publish-site-routing.md +44 -0
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -4
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +2 -2
- package/payload/platform/plugins/contacts/PLUGIN.md +2 -2
- package/payload/platform/plugins/docs/references/internals.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +2 -2
- package/payload/platform/plugins/docs/references/troubleshooting.md +7 -0
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts +2 -1
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts.map +1 -1
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js +27 -21
- package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js.map +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
- package/payload/platform/scripts/setup-account.sh +0 -15
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts +4 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js +50 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +19 -2
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +39 -19
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +24 -0
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +100 -11
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +8 -0
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +7 -2
- package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
- package/payload/server/public/assets/{AdminShell-BcxEZnpu.js → AdminShell-BiEwDSRL.js} +1 -1
- package/payload/server/public/assets/{Checkbox-DL-HdT29.js → Checkbox-COiFXiXL.js} +1 -1
- package/payload/server/public/assets/{admin-DMo5DWKH.js → admin-Ca9ckLZx.js} +1 -1
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-BoUw4oYz.js → architectureDiagram-Q4EWVU46-DI6JZI13.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DOscnO3m.js → blockDiagram-DXYQGD6D-dgDvswLd.js} +1 -1
- package/payload/server/public/assets/{brand-SZW-27T7.css → brand-CAMMU5lz.css} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-aZ1IuZ_F.js → c4Diagram-AHTNJAMY-odaGY4GZ.js} +1 -1
- package/payload/server/public/assets/channel-DfwLbKck.js +1 -0
- package/payload/server/public/assets/{chunk-336JU56O-DS3ksXBG.js → chunk-336JU56O-BpbY8F27.js} +2 -2
- package/payload/server/public/assets/{chunk-426QAEUC-BLRn2STO.js → chunk-426QAEUC-BWbVhEVC.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-BrhOvJ01.js → chunk-4TB4RGXK-C3IfRdvA.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-BZZwZX5Q.js → chunk-5FUZZQ4R-CSRMBAGr.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-YXZKnPCz.js → chunk-5PVQY5BW-CGAe6GJJ.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-DAMCJHKQ.js → chunk-EDXVE4YY-BnXC8cYQ.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-DsD95Bqn.js → chunk-ENJZ2VHE-7qfDalAB.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-DxtrRClx.js → chunk-ICPOFSXX-TRvVGIDw.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-CN4kLK-H.js → chunk-OYMX7WX6-Cfv8vBvI.js} +1 -1
- package/payload/server/public/assets/{chunk-U2HBQHQK-BPTv3Oj1.js → chunk-U2HBQHQK-BfLvbEyu.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-DxBry33v.js → chunk-X2U36JSP-Cj3k-dYC.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-BZ4N75oy.js → chunk-YZCP3GAM-CygW6I0h.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-Dqr161Q1.js → chunk-ZZ45TVLE-CBF3hFJH.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CE0XZQre.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Bj41Y3XD.js +1 -0
- package/payload/server/public/assets/clone-DTd5nONj.js +1 -0
- package/payload/server/public/assets/{dagre-DtZ0wKkH.js → dagre-BTGABQJX.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-BPKhT-ZX.js → dagre-KV5264BT-B2lbkuGd.js} +1 -1
- package/payload/server/public/assets/{data-D6IQdm0R.js → data-CHdaTjgn.js} +1 -1
- package/payload/server/public/assets/{diagram-5BDNPKRD-CATnafi7.js → diagram-5BDNPKRD-BUK4LqT8.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-ChZYQfji.js → diagram-G4DWMVQ6-e49HrvxL.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-DHW6kYd3.js → diagram-MMDJMWI5-k4Kx_qWe.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-CnS4Zci8.js → diagram-TYMM5635-DzavOUdK.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-DKYv2YWp.js → erDiagram-SMLLAGMA-CJKecRhi.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BWvoZWlh.js → flowDiagram-DWJPFMVM-GM7H1TDt.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CpCb-xKy.js → ganttDiagram-T4ZO3ILL-kDEA1A-3.js} +1 -1
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CvHrDKQ-.js → gitGraphDiagram-UUTBAWPF-jVZZdeg2.js} +1 -1
- package/payload/server/public/assets/{graph-labels-CWJy0O4w.js → graph-labels-CavkO4DG.js} +1 -1
- package/payload/server/public/assets/{graph-PkZKVkWD.js → graph-tqCqSngu.js} +1 -1
- package/payload/server/public/assets/{graphlib-DjnyHqGy.js → graphlib-DFU2rJiY.js} +1 -1
- package/payload/server/public/assets/{infoDiagram-42DDH7IO-D_MywcVn.js → infoDiagram-42DDH7IO-CFxVoRBy.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-N9m-f-Eb.js → ishikawaDiagram-UXIWVN3A-CDvZs3Ua.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DE0q7uSM.js → journeyDiagram-VCZTEJTY-DUI1m9vW.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Dj5hb9Bq.js → kanban-definition-6JOO6SKY-C_jSVK7W.js} +1 -1
- package/payload/server/public/assets/{line-C6ph0i4M.js → line-DfwJeo6J.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-BrfmEvCA.js → mermaid-parser.core-BN-2kBfU.js} +1 -1
- package/payload/server/public/assets/{mermaid.core-DYnHKfDv.js → mermaid.core-C6amhHAQ.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DaSaZfhR.js → mindmap-definition-QFDTVHPH-eTKDlw3c.js} +1 -1
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-BAu9Ezbv.js → pieDiagram-DEJITSTG-DNX5VeMZ.js} +1 -1
- package/payload/server/public/assets/{public-UKgkJxKv.js → public-BADBLzOZ.js} +3 -3
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DRsbL1Xd.js → quadrantDiagram-34T5L4WZ-BFnmYiHX.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-B9QcsXV6.js → requirementDiagram-MS252O5E-DvHvTyzu.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DsZbvIlD.js → sankeyDiagram-XADWPNL6-CMFnNT1G.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BlT4O2N8.js → sequenceDiagram-FGHM5R23-BYon1ouU.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-JFgk1K3Y.js → stateDiagram-FHFEXIEX-n6l3hcuJ.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CS46-Mhi.js +1 -0
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-DOsHw1ip.js → timeline-definition-GMOUNBTQ-DFPFHrQF.js} +1 -1
- package/payload/server/public/assets/{useSelectionMode-JDGvwvIk.js → useSelectionMode-iqIyL24g.js} +1 -1
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CQWIhqZY.js → vennDiagram-DHZGUBPP-DZLturYa.js} +1 -1
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-D5Fh7YVL.js → wardleyDiagram-NUSXRM2D-BBt2pdjM.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-C-tINQEs.js → xychartDiagram-5P7HB3ND-DOZxLvEi.js} +1 -1
- package/payload/server/public/data.html +5 -5
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +49 -6
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +0 -109
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +0 -204
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-memory-write-passthrough.test.sh +0 -118
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -337
- package/payload/server/public/assets/channel-CzcSNxDm.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DFOM3vCn.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-HplDfaL1.js +0 -1
- package/payload/server/public/assets/clone-G-2rqnSR.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-NXz27DaV.js +0 -1
- /package/payload/server/public/assets/{brand-SzZmKKjz.js → brand-DfykKss4.js} +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Publish-site routing
|
|
2
|
+
|
|
3
|
+
How a published static site goes from disk to a public URL, so the admin agent can find a site or share a link without guessing.
|
|
4
|
+
|
|
5
|
+
## On-disk layout
|
|
6
|
+
|
|
7
|
+
Every published site lives under the active account:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<accountDir>/sites/<slug>/
|
|
11
|
+
index.html
|
|
12
|
+
…assets
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Where `<accountDir>` resolves to `<installDir>/data/accounts/<accountId>/` and `<slug>` is one or more `/`-separated segments accepted by the `publish-site` tool. To enumerate every published site on the account, list `<accountDir>/sites/` — `LS`, `Glob`, or `Bash ls` all work, pick whichever is in the surface.
|
|
16
|
+
|
|
17
|
+
## URL composition
|
|
18
|
+
|
|
19
|
+
The full published URL is exactly:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
https://<public-hostname>/<slug>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`<public-hostname>` is whatever the `public-hostname` tool returns for this account. That tool reads cloudflared's own ingress config and `alias-domains.json` — the same files the platform server trusts to route — so its answer is deterministic. **Do not guess the hostname**: the public landing host for the product family (for example, `realagent.chat`) is not the publish-site host. Each account's sites are served from the account's own admin tunnel, and the canonical name comes from `public-hostname`.
|
|
26
|
+
|
|
27
|
+
## Recipe — find a site
|
|
28
|
+
|
|
29
|
+
1. `LS <accountDir>/sites/` (or `Glob '<accountDir>/sites/*'`).
|
|
30
|
+
2. Pick the slug whose name matches the request.
|
|
31
|
+
3. Call `public-hostname` once.
|
|
32
|
+
4. The URL is `https://<hostname>/<slug>`.
|
|
33
|
+
|
|
34
|
+
## Recipe — share a published site
|
|
35
|
+
|
|
36
|
+
1. Confirm the slug exists with `LS <accountDir>/sites/<slug>/` (must contain at least an `index.html`).
|
|
37
|
+
2. Call `public-hostname`.
|
|
38
|
+
3. Paste `https://<hostname>/<slug>` to the operator.
|
|
39
|
+
|
|
40
|
+
## Failure modes the agent should not chain
|
|
41
|
+
|
|
42
|
+
- **`browser-render https://<product-host>/<slug>` returning the generic shell.** That host is the marketing landing, not the publish-site host. Resolve the hostname with `public-hostname` instead of typing a guess.
|
|
43
|
+
- **Treating `url-get` `textLength: 0` as "page does not exist".** On a JS-rendered shell the verbatim HTML is fetched but contains no extracted text. The tool returns `ok: true` with `textLength: 0`; use `browser-render` to get the rendered DOM.
|
|
44
|
+
- **Reading `<accountDir>/sites/<slug>/` directly with `Read`.** `Read` is for files, not directories. Use `LS` or `Glob` for directory contents.
|
|
@@ -28,7 +28,7 @@ Admin identity lives in three places that must stay in lockstep:
|
|
|
28
28
|
|
|
29
29
|
## Never write `account.json` directly
|
|
30
30
|
|
|
31
|
-
All account-level mutations (`tier`, `outputStyle`, `thinkingView`, `effort`, `enabledPlugins`, `admins[]`, agent settings) go through the dedicated MCP tools: `account-update`, `plugin-toggle-enabled`, `admin-add`, `admin-remove`.
|
|
31
|
+
All account-level mutations (`tier`, `outputStyle`, `thinkingView`, `effort`, `enabledPlugins`, `admins[]`, agent settings) go through the dedicated MCP tools: `account-update`, `plugin-toggle-enabled`, `admin-add`, `admin-remove`. Direct `Edit` or `Write` on `account.json` is forbidden by IDENTITY.md doctrine — there is no server-side gate, so the doctrine line is the only thing standing between an agent slip and a silently corrupted account file. `tier` and `purchasedPlugins` derive from a Rubytech-signed entitlement payload on commercial installs, so a hand-edit is silently void.
|
|
32
32
|
|
|
33
33
|
`tier` is the one account-level field with no admin-agent surface — `account-update` excludes it deliberately, because tier is privilege-bearing and the agent must not self-elevate. When refusing a tier-cap violation (`admin-add` against a full slot, etc.), point the owner at the founder-side path: `npx @rubytech/create-<brand>-code@latest --tier <solo|family|pro>` run on the device by the founder. On commercial installs the founder delivers a signed payload instead: `--entitlement-base64 <base64>`.
|
|
34
34
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:cabadf29cd1f98c47b4f93c23339ee577ce7bb41092d085d01268b947165a1d5
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -220,7 +220,7 @@ Each session row also carries a small muted timestamp crumb under the name showi
|
|
|
220
220
|
|
|
221
221
|
**Two spawn surfaces, one primitive (Task 573).** The manager runs two on-device spawn surfaces, both backed by the same primitive: **node-pty wrapped in `systemd-run --user --scope`** (via `index.ts::spawnPtyAdapter`).
|
|
222
222
|
|
|
223
|
-
- **`claude rc` daemon** — spawned at platform boot by `rc-daemon.ts`. One supervised daemon per account; owns the long-lived composer session that backs claude.ai/code Remote Control. Master fd held for the daemon's lifetime, released on natural exit / restart.
|
|
223
|
+
- **`claude rc` daemon** — spawned at platform boot by `rc-daemon.ts`. One supervised daemon per account; owns the long-lived composer session that backs claude.ai/code Remote Control. Master fd held for the daemon's lifetime, released on natural exit / restart. **Headless consent pre-seed (Task 578).** Before the first spawn, `ensureRemoteControlConsent` writes `{"remoteControlAtStartup": true}` into `$CLAUDE_CONFIG_DIR/.claude.json` (read-merge-write, atomic tmp+rename, idempotent). Without this, headless `claude rc` hangs at `Enable Remote Control? (y/n)` — nothing answers, the supervisor restarts the child, eventually marks the daemon permanently-failed. The key is the same one `claude` itself writes when the user answers `y` at the prompt; siblings (`teammateMode`, `hasUsedRemoteControl`, claude's auth blocks) are preserved.
|
|
224
224
|
- **`claude --remote-control` on-device sidebar spawn** — spawned per-click by `/rc-spawn` in `http-server.ts`. One PTY per click; the manager holds the master fd **for the session's entire lifetime**. The pty master IS the live session — claude operates on the slave, and closing the master hangs up the slave. Valid master-release points: (1) explicit operator teardown — `/stop` → `stopSession` → `op=archive-release` — and (2) the natural-exit path inside `pty.onExit → handlePtyNaturalExit`.
|
|
225
225
|
|
|
226
226
|
Inside the scope, `sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <args...>` keeps claude resident across PTY master-close (SIGHUP trap) and preserves the pid through the exec chain. The earlier `script(1)` wrap and the non-PTY scope primitive (Tasks 552/556/562) are gone; node-pty allocates the TTY directly.
|
|
@@ -244,7 +244,7 @@ Inside the scope, `sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <args...>` keep
|
|
|
244
244
|
|
|
245
245
|
`verified=true` requires `master-fd=closed` AND `fdDelta>=1` AND `trackerRemoved=true`. `master-fd=close-failed` is logged at error level (`[rc-spawn-error]` prefix) — never swallowed; the next post-archive sweep is the catch-net.
|
|
246
246
|
|
|
247
|
-
**Cross-arm `[rc-life]` schema.** rc-spawn and rc-daemon emit a shared log shape so the populations can be compared from `server.log` alone. One spawn's full lifeline is `grep <unitToken>`; one surface's signature is `grep 'source=rc-spawn'` or `'source=rc-daemon'`. Success on both surfaces is `op=pidfile-present`; failure is `op=spawn-failed` / `op=early-exit` / `op=wait-pid-failed`. Full schema and operator runbook in [`.docs/rc-life-observability.md`](../../../.docs/rc-life-observability.md).
|
|
247
|
+
**Cross-arm `[rc-life]` schema.** rc-spawn and rc-daemon emit a shared log shape so the populations can be compared from `server.log` alone. One spawn's full lifeline is `grep <unitToken>`; one surface's signature is `grep 'source=rc-spawn'` or `'source=rc-daemon'`. Success on both surfaces is `op=pidfile-present`; failure is `op=spawn-failed` / `op=early-exit` / `op=wait-pid-failed`. Full schema and operator runbook in [`.docs/rc-life-observability.md`](../../../.docs/rc-life-observability.md). **Measured `remoteBound` (Task 578).** The rc-daemon liveness emit reports `remoteBound` as a measured value flipped by `detectRcHandshake` once the daemon's own post-bind output (`Capacity:` header or an `N of M` capacity line) is seen on the PTY. A daemon that is alive but not registered to Remote Control therefore prints `pidAlive=true remoteBound=false` — previously masked by a hardcoded literal. A class-guard test (`rc-life-literals.test.ts`) scans every `emitRcLife` call across the manager and fails if any status-shaped field is set to a boolean/string literal. The captured PTY output is now dumped on **every** exit (not only fast exits), prefix `exit-output` or `fast-exit-output`, so a late-life prompt-hang is no longer invisible.
|
|
248
248
|
|
|
249
249
|
**Post-archive fd sweep (Task 558).** Independent of spawn/archive request traffic, the manager runs a 60 s sweep that walks both directions of the master-fd invariant:
|
|
250
250
|
|
|
@@ -3134,7 +3134,7 @@ Each log entry includes the tool name and a truncated conversation ID for correl
|
|
|
3134
3134
|
|
|
3135
3135
|
Every durable action — cloudflare tunnel-login, brand publish, future deterministic flows — emits a `:Task {kind:"<flow>"}` node carrying the action's lifecycle and a `:PRODUCED` edge to every entity the action created. This makes the graph traversable from the originating Conversation to every entity created during it via `(c)<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(e)` — answering "what did this turn produce" in one Cypher hop.
|
|
3136
3136
|
|
|
3137
|
-
The doctrine is
|
|
3137
|
+
The doctrine is observed at the storage primitive: writes to `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:LocalBusiness`, `:CloudflareTunnel`, or `:CloudflareHostname` should carry an inbound `:PRODUCED` edge whose source is one of `:Task`, `:Conversation`, or `:Message`. Subtype labels like `:AdminConversation`, `:UserMessage`, `:AssistantMessage`, `:AdminMessage` qualify because the gate checks the full `labels()` array. Bootstrap writes (PIN-setup, schema migrations, lazy first-session UserProfile creation) are exempt via `createdBy.agent === 'system'`. When no qualifying edge resolves, the primitive emits a `[graph-write] warn reason=missing-provenance labels=<csv> agent=<agentLabel>` line and the write proceeds (Task 580 relaxed this from a hard reject — the composer-spawned admin path inherits a bare per-account env that never receives the `SESSION_NODE_ID` stamp, so the throw was failing every direct admin contact-create / memory-write for a gated label).
|
|
3138
3138
|
|
|
3139
3139
|
Two surfaces emit the lifecycle: agent-driven actions call `work-create`/`work-update`/`work-complete` over MCP (`work-create` accepts `kind`, the canonical `inputsProvided` call-shape record, `inputs` + `inputSchema` for the operator-meaningful form payload, and `raisedDuringConversationKey` to resolve the `RAISED_DURING` edge). Shell-driven actions wrap their script invocation in [platform/ui/app/lib/cloudflare-task-tracker.ts](../../../ui/app/lib/cloudflare-task-tracker.ts) (cloudflare is the first; installer / brand-publish / OAuth-login deferred). Both surfaces emit the same `[task] action-start|step|done` log lines so operators can grep one channel uniformly. Both also call the central `redactSecrets` primitive ([platform/lib/task-secrets/](../../../lib/task-secrets/)) to strip schema-tagged secret keys before persisting `inputs.<field>` props on the Task — see `.docs/neo4j.md § Audit Task input contract` for the contract that replaces per-kind allow-lists.
|
|
3140
3140
|
|
|
@@ -3630,6 +3630,13 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
|
|
|
3630
3630
|
2. `diff <(jq .claudeAiOauth.accessToken ~/.maxy/.claude/.credentials.json) <(jq .claudeAiOauth.accessToken ~/.realagent/.claude/.credentials.json)` — must be non-empty after each brand's operator has run `claude /login` against distinct Anthropic accounts; if it's empty, both brands are still logged in to the same account (operator action, not a code bug).
|
|
3631
3631
|
3. `grep "\[install\] claude-creds pickup" ~/.${brand}/logs/install-*.log` — fires once on the first post-Task-923 install of any brand and moves the legacy `~/.claude/.credentials.json` into that brand's path. Subsequent brands install with no credentials and require a fresh `claude /login` inside that brand's chat (which writes to the brand-scoped path because the systemd unit env is in scope).
|
|
3632
3632
|
|
|
3633
|
+
**All sessions on the brand stopped responding after a token expiry.** Symptom on the operator side: every spawn dies at `pid-file-timeout` and the dashboard health probe reports auth dead. Diagnose the OAuth refresh path before anything else:
|
|
3634
|
+
|
|
3635
|
+
1. `tail -n 300 ~/.${brand}/logs/server.log | grep -E 'auth-refresh|auth-health|invalid_grant'` — `op=lock-acquired` proves the cross-process lock is in play (Task 576). `op=skipped-fresh` means a sibling process (the admin server or a `claude` binary) already rotated the tokens during the lock wait — expected, healthy. `op=renewed expiresAt=…` is the only line that means a network refresh actually ran.
|
|
3636
|
+
2. `outcome=fail-token` or `invalid_grant` lines mean Anthropic rejected the refresh token itself (revoked or expired beyond the rotation window). The brand needs a fresh `claude /login`. Pre-576 the most common cause was the admin server and a spawned `claude` racing to rotate the same single-use refresh token; that race is now serialised by the file lock at `~/.${brand}/.claude/.credentials.json.lock` and a re-read after the lock skips redundant refreshes.
|
|
3637
|
+
3. `grep '\[auth-health\]' ~/.${brand}/logs/server.log | tail -n 5` — the heartbeat fires every five minutes. `status=dead expiresIn=...` means the refresh token is gone; only a re-login fixes it. `status=ok` heartbeats with no spawns in between mean the credentials file is healthy and the failure lives elsewhere.
|
|
3638
|
+
4. The spawn-failure surface now carries `reason=auth-refresh-failed` (with `authStatus` in the JSON body) instead of generic `pid-file-timeout` whenever the credentials file is in `dead` or `expired` state at the moment of failure — visible in `grep '\[spawn-failed\]'` on server.log.
|
|
3639
|
+
|
|
3633
3640
|
---
|
|
3634
3641
|
|
|
3635
3642
|
## Memory Not Working
|
|
@@ -34,7 +34,7 @@ Built-in plugins under `$PLATFORM_ROOT/plugins/`:
|
|
|
34
34
|
|
|
35
35
|
- Enable: call `plugin-toggle-enabled` with `pluginName` and `action: "enable"`. Activates the plugin's behaviour embed and MCP server from the next session. Plugins with scheduled behaviour (declared via `lifecycle` in PLUGIN.md frontmatter) are activated automatically by the platform heartbeat within one minute — no separate setup command needed.
|
|
36
36
|
- Disable: call `plugin-toggle-enabled` with `pluginName` and `action: "disable"`. Deactivates from the next session. Any scheduled Events owned by the plugin (`sourcePlugin` field) are cancelled automatically by the platform heartbeat.
|
|
37
|
-
- The tool refuses core plugins (admin, memory, docs, cloudflare) and refuses plugins not installed under `$PLATFORM_ROOT/plugins/`. Direct `Edit` of `account.json` is
|
|
37
|
+
- The tool refuses core plugins (admin, memory, docs, cloudflare) and refuses plugins not installed under `$PLATFORM_ROOT/plugins/`. Direct `Edit` of `account.json` is forbidden by IDENTITY.md doctrine — `plugin-toggle-enabled` is the only legitimate path.
|
|
38
38
|
|
|
39
39
|
## Premium Plugins
|
|
40
40
|
|
|
@@ -50,7 +50,7 @@ When the user asks about a specific premium plugin (e.g., "tell me about the tea
|
|
|
50
50
|
|
|
51
51
|
### Recording a purchase
|
|
52
52
|
|
|
53
|
-
`purchasedPlugins` is an entitlement-bearing field: the effective list derives from the Rubytech-signed entitlement payload, not from raw `account.json`.
|
|
53
|
+
`purchasedPlugins` is an entitlement-bearing field: the effective list derives from the Rubytech-signed entitlement payload, not from raw `account.json`. IDENTITY.md doctrine forbids any agent write to `account.json`. Direct purchase recording is therefore unavailable to the agent.
|
|
54
54
|
|
|
55
55
|
- **Production path:** the Rubytech-side issuance endpoint signs an updated entitlement payload after a Stripe purchase event and delivers it to `~/<configDir>/entitlement.json`. The verifier picks up the new payload at the next session start.
|
|
56
56
|
- **Pre-launch / dev path:** the founder runs `node platform/scripts/generate-entitlement-fixture.mjs sign --account-id <id> --email <e> --tier <t> --plugins a,b,c --out ~/<configDir>/entitlement.json` to produce a signed test payload. The agent does not invoke this script.
|
|
@@ -55,8 +55,8 @@ Tools are available via the `contacts` MCP server.
|
|
|
55
55
|
|
|
56
56
|
## Provenance anchoring
|
|
57
57
|
|
|
58
|
-
`contact-create` writes a `:Person` node, which is one of the action-provenance-gated labels in `writeNodeWithEdges`. The write
|
|
58
|
+
`contact-create` writes a `:Person` node, which is one of the action-provenance-gated labels in `writeNodeWithEdges`. The write should carry an inbound `:PRODUCED` edge from `:Task`, `:Conversation`, or `:Message`. There is no agent-visible field for this — the admin server stamps `SESSION_NODE_ID` (the `sessionId` UUID of the active `:AdminConversation`) at MCP spawn time, and the wrapper auto-injects the `:PRODUCED` edge via `injectConversationProvenance`. The helper MATCHes `(c:Conversation {sessionId, accountId})` — account isolation is enforced inside the natural key, not as a separate gate, so a cross-account env-stamp returns zero rows and never injects.
|
|
59
59
|
|
|
60
|
-
The agent passes whichever organic relationships are appropriate (`PARTICIPATES_IN` to a Conversation, `WORKS_FOR` to an Organization, `KNOWN_VIA` to a referral source) — the provenance edge is composed automatically and is not an agent concern. When the env-stamp resolution fails (loud `[mcp:contacts] [provenance-missing] reason=<...>`), the downstream gate emits `missing-provenance
|
|
60
|
+
The agent passes whichever organic relationships are appropriate (`PARTICIPATES_IN` to a Conversation, `WORKS_FOR` to an Organization, `KNOWN_VIA` to a referral source) — the provenance edge is composed automatically and is not an agent concern. When the env-stamp resolution fails (loud `[mcp:contacts] [provenance-missing] reason=<...>`), the downstream gate emits a `[graph-write] warn reason=missing-provenance labels=<csv> agent=<agentLabel>` line and the write proceeds (Task 580 relaxed this from a hard reject). The warn carries the labels and agent so operators can quantify how often the gap fires; the upstream `[provenance-missing]` line names the resolution failure that caused it.
|
|
61
61
|
|
|
62
62
|
See [.docs/neo4j.md § Process provenance doctrine](../../../.docs/neo4j.md) for the doctrine and observability surface.
|
|
@@ -502,7 +502,7 @@ Each log entry includes the tool name and a truncated conversation ID for correl
|
|
|
502
502
|
|
|
503
503
|
Every durable action — cloudflare tunnel-login, brand publish, future deterministic flows — emits a `:Task {kind:"<flow>"}` node carrying the action's lifecycle and a `:PRODUCED` edge to every entity the action created. This makes the graph traversable from the originating Conversation to every entity created during it via `(c)<-[:RAISED_DURING]-(t:Task)-[:PRODUCED]->(e)` — answering "what did this turn produce" in one Cypher hop.
|
|
504
504
|
|
|
505
|
-
The doctrine is
|
|
505
|
+
The doctrine is observed at the storage primitive: writes to `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:LocalBusiness`, `:CloudflareTunnel`, or `:CloudflareHostname` should carry an inbound `:PRODUCED` edge whose source is one of `:Task`, `:Conversation`, or `:Message`. Subtype labels like `:AdminConversation`, `:UserMessage`, `:AssistantMessage`, `:AdminMessage` qualify because the gate checks the full `labels()` array. Bootstrap writes (PIN-setup, schema migrations, lazy first-session UserProfile creation) are exempt via `createdBy.agent === 'system'`. When no qualifying edge resolves, the primitive emits a `[graph-write] warn reason=missing-provenance labels=<csv> agent=<agentLabel>` line and the write proceeds (Task 580 relaxed this from a hard reject — the composer-spawned admin path inherits a bare per-account env that never receives the `SESSION_NODE_ID` stamp, so the throw was failing every direct admin contact-create / memory-write for a gated label).
|
|
506
506
|
|
|
507
507
|
Two surfaces emit the lifecycle: agent-driven actions call `work-create`/`work-update`/`work-complete` over MCP (`work-create` accepts `kind`, the canonical `inputsProvided` call-shape record, `inputs` + `inputSchema` for the operator-meaningful form payload, and `raisedDuringConversationKey` to resolve the `RAISED_DURING` edge). Shell-driven actions wrap their script invocation in [platform/ui/app/lib/cloudflare-task-tracker.ts](../../../ui/app/lib/cloudflare-task-tracker.ts) (cloudflare is the first; installer / brand-publish / OAuth-login deferred). Both surfaces emit the same `[task] action-start|step|done` log lines so operators can grep one channel uniformly. Both also call the central `redactSecrets` primitive ([platform/lib/task-secrets/](../../../lib/task-secrets/)) to strip schema-tagged secret keys before persisting `inputs.<field>` props on the Task — see `.docs/neo4j.md § Audit Task input contract` for the contract that replaces per-kind allow-lists.
|
|
508
508
|
|
|
@@ -108,7 +108,7 @@ Each session row also carries a small muted timestamp crumb under the name showi
|
|
|
108
108
|
|
|
109
109
|
**Two spawn surfaces, one primitive (Task 573).** The manager runs two on-device spawn surfaces, both backed by the same primitive: **node-pty wrapped in `systemd-run --user --scope`** (via `index.ts::spawnPtyAdapter`).
|
|
110
110
|
|
|
111
|
-
- **`claude rc` daemon** — spawned at platform boot by `rc-daemon.ts`. One supervised daemon per account; owns the long-lived composer session that backs claude.ai/code Remote Control. Master fd held for the daemon's lifetime, released on natural exit / restart.
|
|
111
|
+
- **`claude rc` daemon** — spawned at platform boot by `rc-daemon.ts`. One supervised daemon per account; owns the long-lived composer session that backs claude.ai/code Remote Control. Master fd held for the daemon's lifetime, released on natural exit / restart. **Headless consent pre-seed (Task 578).** Before the first spawn, `ensureRemoteControlConsent` writes `{"remoteControlAtStartup": true}` into `$CLAUDE_CONFIG_DIR/.claude.json` (read-merge-write, atomic tmp+rename, idempotent). Without this, headless `claude rc` hangs at `Enable Remote Control? (y/n)` — nothing answers, the supervisor restarts the child, eventually marks the daemon permanently-failed. The key is the same one `claude` itself writes when the user answers `y` at the prompt; siblings (`teammateMode`, `hasUsedRemoteControl`, claude's auth blocks) are preserved.
|
|
112
112
|
- **`claude --remote-control` on-device sidebar spawn** — spawned per-click by `/rc-spawn` in `http-server.ts`. One PTY per click; the manager holds the master fd **for the session's entire lifetime**. The pty master IS the live session — claude operates on the slave, and closing the master hangs up the slave. Valid master-release points: (1) explicit operator teardown — `/stop` → `stopSession` → `op=archive-release` — and (2) the natural-exit path inside `pty.onExit → handlePtyNaturalExit`.
|
|
113
113
|
|
|
114
114
|
Inside the scope, `sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <args...>` keeps claude resident across PTY master-close (SIGHUP trap) and preserves the pid through the exec chain. The earlier `script(1)` wrap and the non-PTY scope primitive (Tasks 552/556/562) are gone; node-pty allocates the TTY directly.
|
|
@@ -132,7 +132,7 @@ Inside the scope, `sh -c 'trap "" HUP; exec "$@"' sh <claudeBin> <args...>` keep
|
|
|
132
132
|
|
|
133
133
|
`verified=true` requires `master-fd=closed` AND `fdDelta>=1` AND `trackerRemoved=true`. `master-fd=close-failed` is logged at error level (`[rc-spawn-error]` prefix) — never swallowed; the next post-archive sweep is the catch-net.
|
|
134
134
|
|
|
135
|
-
**Cross-arm `[rc-life]` schema.** rc-spawn and rc-daemon emit a shared log shape so the populations can be compared from `server.log` alone. One spawn's full lifeline is `grep <unitToken>`; one surface's signature is `grep 'source=rc-spawn'` or `'source=rc-daemon'`. Success on both surfaces is `op=pidfile-present`; failure is `op=spawn-failed` / `op=early-exit` / `op=wait-pid-failed`. Full schema and operator runbook in [`.docs/rc-life-observability.md`](../../../.docs/rc-life-observability.md).
|
|
135
|
+
**Cross-arm `[rc-life]` schema.** rc-spawn and rc-daemon emit a shared log shape so the populations can be compared from `server.log` alone. One spawn's full lifeline is `grep <unitToken>`; one surface's signature is `grep 'source=rc-spawn'` or `'source=rc-daemon'`. Success on both surfaces is `op=pidfile-present`; failure is `op=spawn-failed` / `op=early-exit` / `op=wait-pid-failed`. Full schema and operator runbook in [`.docs/rc-life-observability.md`](../../../.docs/rc-life-observability.md). **Measured `remoteBound` (Task 578).** The rc-daemon liveness emit reports `remoteBound` as a measured value flipped by `detectRcHandshake` once the daemon's own post-bind output (`Capacity:` header or an `N of M` capacity line) is seen on the PTY. A daemon that is alive but not registered to Remote Control therefore prints `pidAlive=true remoteBound=false` — previously masked by a hardcoded literal. A class-guard test (`rc-life-literals.test.ts`) scans every `emitRcLife` call across the manager and fails if any status-shaped field is set to a boolean/string literal. The captured PTY output is now dumped on **every** exit (not only fast exits), prefix `exit-output` or `fast-exit-output`, so a late-life prompt-hang is no longer invisible.
|
|
136
136
|
|
|
137
137
|
**Post-archive fd sweep (Task 558).** Independent of spawn/archive request traffic, the manager runs a 60 s sweep that walks both directions of the master-fd invariant:
|
|
138
138
|
|
|
@@ -89,6 +89,13 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
|
|
|
89
89
|
2. `diff <(jq .claudeAiOauth.accessToken ~/.maxy/.claude/.credentials.json) <(jq .claudeAiOauth.accessToken ~/.realagent/.claude/.credentials.json)` — must be non-empty after each brand's operator has run `claude /login` against distinct Anthropic accounts; if it's empty, both brands are still logged in to the same account (operator action, not a code bug).
|
|
90
90
|
3. `grep "\[install\] claude-creds pickup" ~/.${brand}/logs/install-*.log` — fires once on the first post-Task-923 install of any brand and moves the legacy `~/.claude/.credentials.json` into that brand's path. Subsequent brands install with no credentials and require a fresh `claude /login` inside that brand's chat (which writes to the brand-scoped path because the systemd unit env is in scope).
|
|
91
91
|
|
|
92
|
+
**All sessions on the brand stopped responding after a token expiry.** Symptom on the operator side: every spawn dies at `pid-file-timeout` and the dashboard health probe reports auth dead. Diagnose the OAuth refresh path before anything else:
|
|
93
|
+
|
|
94
|
+
1. `tail -n 300 ~/.${brand}/logs/server.log | grep -E 'auth-refresh|auth-health|invalid_grant'` — `op=lock-acquired` proves the cross-process lock is in play (Task 576). `op=skipped-fresh` means a sibling process (the admin server or a `claude` binary) already rotated the tokens during the lock wait — expected, healthy. `op=renewed expiresAt=…` is the only line that means a network refresh actually ran.
|
|
95
|
+
2. `outcome=fail-token` or `invalid_grant` lines mean Anthropic rejected the refresh token itself (revoked or expired beyond the rotation window). The brand needs a fresh `claude /login`. Pre-576 the most common cause was the admin server and a spawned `claude` racing to rotate the same single-use refresh token; that race is now serialised by the file lock at `~/.${brand}/.claude/.credentials.json.lock` and a re-read after the lock skips redundant refreshes.
|
|
96
|
+
3. `grep '\[auth-health\]' ~/.${brand}/logs/server.log | tail -n 5` — the heartbeat fires every five minutes. `status=dead expiresIn=...` means the refresh token is gone; only a re-login fixes it. `status=ok` heartbeats with no spawns in between mean the credentials file is healthy and the failure lives elsewhere.
|
|
97
|
+
4. The spawn-failure surface now carries `reason=auth-refresh-failed` (with `authStatus` in the JSON body) instead of generic `pid-file-timeout` whenever the credentials file is in `dead` or `expired` state at the moment of failure — visible in `grep '\[spawn-failed\]'` on server.log.
|
|
98
|
+
|
|
92
99
|
---
|
|
93
100
|
|
|
94
101
|
## Memory Not Working
|
|
@@ -175,7 +175,7 @@ Every node in the graph sits under a parent in the canonical chain `LocalBusines
|
|
|
175
175
|
|
|
176
176
|
## Anchoring writes to provenance — `producedByTaskId` and the conversation env-stamp
|
|
177
177
|
|
|
178
|
-
Writes targeting `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:LocalBusiness`, `:CloudflareTunnel`, or `:CloudflareHostname`
|
|
178
|
+
Writes targeting `:Person`, `:UserProfile`, `:AdminUser`, `:Organization`, `:LocalBusiness`, `:CloudflareTunnel`, or `:CloudflareHostname` expect an inbound `:PRODUCED` edge whose source is one of `:Task`, `:Conversation`, or `:Message` (deterministic bootstrap paths run as `createdBy.agent === 'system'` and are exempt). The expectation is enforced as a soft warning, not a reject: when no qualifying edge resolves the storage primitive emits a single `[graph-write] warn reason=missing-provenance labels=<csv> agent=<agentLabel>` line on stderr and the write proceeds. Task 580 relaxed this from a hard throw — the composer-spawned admin path inherits a bare per-account env that never receives the `SESSION_NODE_ID` stamp, so the throw was failing every direct admin contact-create / memory-write for a gated label. The two stamping surfaces below remain the right thing to use; the warn line is the operator-visible signal for how often the gap fires. Two surfaces feed the stamp:
|
|
179
179
|
|
|
180
180
|
- **Workflow path — `producedByTaskId`.** `memory-write` accepts an optional `producedByTaskId` parameter. When set, the platform composes an inbound `:PRODUCED` edge from that `:Task` node into the write's `relationships` array before the write runs. The Task and the new node must share the same `accountId`; mismatch is rejected loud. The typical pattern: call `work-create` at the start of an autonomous flow (onboarding skill, cloudflare tunnel-login) with `kind` and `raisedDuringConversationKey`; capture the returned `taskId`; pass it as `producedByTaskId` on every subsequent `memory-write` for one of the gated labels.
|
|
181
181
|
|
|
@@ -99,8 +99,8 @@ graph as a single action.
|
|
|
99
99
|
**Never run `cypher-shell` via `Bash` to work around the read-only tool.**
|
|
100
100
|
The admin session's `Bash` surface has no path to the per-brand Neo4j
|
|
101
101
|
password (it lives in `${PLATFORM_ROOT}/config/.neo4j-password`,
|
|
102
|
-
unreachable from the admin account's `Bash` cwd, and
|
|
103
|
-
|
|
102
|
+
unreachable from the admin account's `Bash` cwd, and IDENTITY.md doctrine
|
|
103
|
+
forbids `Bash` against the code tree anyway — `.docs/neo4j.md`'s migration
|
|
104
104
|
section is an operator-run runbook, not a path you should replicate in
|
|
105
105
|
chat). Every write intent has a schema-aware tool above. If none of them
|
|
106
106
|
fits, tell the user plainly: *"This operation is not in the admin tool
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export interface UrlGetParams {
|
|
2
2
|
url: string;
|
|
3
3
|
}
|
|
4
|
-
export type UrlGetError = "fetch" | "http" | "non-html" | "challenge" | "
|
|
4
|
+
export type UrlGetError = "fetch" | "http" | "non-html" | "challenge" | "no-account-dir";
|
|
5
5
|
export type UrlGetResult = {
|
|
6
6
|
ok: true;
|
|
7
7
|
url: string;
|
|
8
8
|
httpStatus: number;
|
|
9
9
|
bytes: number;
|
|
10
|
+
textLength: number;
|
|
10
11
|
referencePath: string;
|
|
11
12
|
summary: string;
|
|
12
13
|
summarized: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-get.d.ts","sourceRoot":"","sources":["../../src/tools/url-get.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"url-get.d.ts","sourceRoot":"","sources":["../../src/tools/url-get.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,gBAAgB,CAAC;AAEzF,MAAM,MAAM,YAAY,GACpB;IACE,EAAE,EAAE,IAAI,CAAC;IACT,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;CACrB,GACD;IACE,EAAE,EAAE,KAAK,CAAC;IACV,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,WAAW,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAyCN,wBAAsB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA2GxE"}
|
|
@@ -98,16 +98,11 @@ export async function urlGet(params) {
|
|
|
98
98
|
};
|
|
99
99
|
}
|
|
100
100
|
const markdown = NodeHtmlMarkdown.translate(html).trim();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
error: "empty",
|
|
107
|
-
httpStatus,
|
|
108
|
-
message: "page has no readable text content",
|
|
109
|
-
};
|
|
110
|
-
}
|
|
101
|
+
const textLength = markdown.length;
|
|
102
|
+
// HTTP 200 is success. Empty extractable text is a property of the response,
|
|
103
|
+
// not an error: a JS-rendered SPA shell legitimately has zero markdown bytes
|
|
104
|
+
// until the bundle runs. Surface `textLength` so the caller can decide
|
|
105
|
+
// whether to fall back to browser-render; never invert the success signal.
|
|
111
106
|
const accountDir = resolveAccountDir();
|
|
112
107
|
if (!accountDir) {
|
|
113
108
|
console.error(`[url-get] url=${url} error=no-account-dir`);
|
|
@@ -128,20 +123,31 @@ export async function urlGet(params) {
|
|
|
128
123
|
await writeFile(referencePath, fileBody, "utf-8");
|
|
129
124
|
// Summary is derived and best-effort. The verbatim file is the contract and
|
|
130
125
|
// is already on disk; a summary failure degrades gracefully rather than
|
|
131
|
-
// discarding the artefact.
|
|
126
|
+
// discarding the artefact. On empty extractable text we short-circuit with a
|
|
127
|
+
// synthesised summary — calling the LLM on zero input either fails or
|
|
128
|
+
// hallucinates, neither of which serves the agent.
|
|
132
129
|
let summary;
|
|
133
130
|
let summarized;
|
|
134
|
-
|
|
135
|
-
summary =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
140
|
-
summary = `<summary unavailable: ${detail}> — read the reference file at ${referencePath} for the full text.`;
|
|
131
|
+
if (textLength === 0) {
|
|
132
|
+
summary =
|
|
133
|
+
`Page returned HTTP ${httpStatus} but had no extractable text content ` +
|
|
134
|
+
`(textLength=0; bytes=${bytes}). The response is likely a JavaScript-` +
|
|
135
|
+
`rendered shell; use browser-render to retrieve the rendered DOM.`;
|
|
141
136
|
summarized = false;
|
|
142
137
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
else {
|
|
139
|
+
try {
|
|
140
|
+
summary = await summarise(url, markdown.slice(0, SUMMARY_INPUT_CAP));
|
|
141
|
+
summarized = true;
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
145
|
+
summary = `<summary unavailable: ${detail}> — read the reference file at ${referencePath} for the full text.`;
|
|
146
|
+
summarized = false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
console.error(`[url-get] url=${url} httpStatus=${httpStatus} bytes=${bytes} textLength=${textLength} ` +
|
|
150
|
+
`refPath=${referencePath} summarized=${summarized} (${Date.now() - t0}ms)`);
|
|
151
|
+
return { ok: true, url, httpStatus, bytes, textLength, referencePath, summary, summarized };
|
|
146
152
|
}
|
|
147
153
|
//# sourceMappingURL=url-get.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-get.js","sourceRoot":"","sources":["../../src/tools/url-get.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,iGAAiG;AACjG,2FAA2F;AAC3F,2FAA2F;AAC3F,+FAA+F;AAC/F,mGAAmG;AACnG,kGAAkG;AAClG,qGAAqG;AACrG,EAAE;AACF,4EAA4E;AAC5E,sEAAsE;AACtE,+EAA+E;AAC/E,yEAAyE;AACzE,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"url-get.js","sourceRoot":"","sources":["../../src/tools/url-get.ts"],"names":[],"mappings":"AAAA,mEAAmE;AACnE,EAAE;AACF,iGAAiG;AACjG,2FAA2F;AAC3F,2FAA2F;AAC3F,+FAA+F;AAC/F,mGAAmG;AACnG,kGAAkG;AAClG,qGAAqG;AACrG,EAAE;AACF,4EAA4E;AAC5E,sEAAsE;AACtE,+EAA+E;AAC/E,yEAAyE;AACzE,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA2BhD,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAClC,MAAM,UAAU,GACd,qEAAqE;IACrE,gDAAgD,CAAC;AAEnD,4EAA4E;AAC5E,4EAA4E;AAC5E,2DAA2D;AAC3D,MAAM,oBAAoB,GAAG;IAC3B,8BAA8B;IAC9B,yBAAyB;IACzB,2CAA2C;IAC3C,wCAAwC;IACxC,kCAAkC;IAClC,sBAAsB;CACvB,CAAC;AAEF;;;;mDAImD;AACnD,SAAS,iBAAiB;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACvC,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE;QAAE,OAAO,MAAM,CAAC;IAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACzC,IAAI,YAAY,IAAI,SAAS,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACvF,OAAO,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACnD,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAoB;IAC/C,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IACvB,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEtB,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACrB,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;YAC7C,OAAO,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,iCAAiC,EAAE;SACjF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,uBAAuB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACpF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC;IAC9B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,eAAe,UAAU,EAAE,CAAC,CAAC;QAC/D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,UAAU,EAAE,EAAE,CAAC;IACtF,CAAC;IAED,MAAM,WAAW,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1E,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACxF,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,+BAA+B,WAAW,EAAE,CAAC,CAAC;QAChF,OAAO;YACL,EAAE,EAAE,KAAK;YACT,GAAG;YACH,KAAK,EAAE,UAAU;YACjB,UAAU;YACV,OAAO,EAAE,6BAA6B,WAAW,8CAA8C;SAChG,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IAC9B,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE/C,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,kBAAkB,CAAC,CAAC;QACtD,OAAO;YACL,EAAE,EAAE,KAAK;YACT,GAAG;YACH,KAAK,EAAE,WAAW;YAClB,UAAU;YACV,OAAO,EAAE,yDAAyD;SACnE,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,gBAAgB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IACzD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;IAEnC,6EAA6E;IAC7E,6EAA6E;IAC7E,uEAAuE;IACvE,2EAA2E;IAE3E,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;IACvC,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,uBAAuB,CAAC,CAAC;QAC3D,OAAO;YACL,EAAE,EAAE,KAAK;YACT,GAAG;YACH,KAAK,EAAE,gBAAgB;YACvB,UAAU;YACV,OAAO,EAAE,mFAAmF;SAC7F,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,uBAAuB,GAAG,cAAc,SAAS,eAAe,UAAU,WAAW,QAAQ,IAAI,CAAC;IACnH,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,aAAa,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAElD,4EAA4E;IAC5E,wEAAwE;IACxE,6EAA6E;IAC7E,sEAAsE;IACtE,mDAAmD;IACnD,IAAI,OAAe,CAAC;IACpB,IAAI,UAAmB,CAAC;IACxB,IAAI,UAAU,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,sBAAsB,UAAU,uCAAuC;gBACvE,wBAAwB,KAAK,yCAAyC;gBACtE,kEAAkE,CAAC;QACrE,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC;YACrE,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO,GAAG,yBAAyB,MAAM,kCAAkC,aAAa,qBAAqB,CAAC;YAC9G,UAAU,GAAG,KAAK,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,KAAK,CACX,iBAAiB,GAAG,eAAe,UAAU,UAAU,KAAK,eAAe,UAAU,GAAG;QACtF,WAAW,aAAa,eAAe,UAAU,KAAK,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAC7E,CAAC;IAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AAC9F,CAAC"}
|
|
@@ -41,6 +41,9 @@ const ALLOWLIST = new Set([
|
|
|
41
41
|
// Same vi.hoisted pattern: Task 549's sidebar-sessions test pre-builds a
|
|
42
42
|
// tmpdir before the module-under-test's ACCOUNTS_DIR mock is wired.
|
|
43
43
|
'platform/ui/server/routes/admin/__tests__/sidebar-sessions.test.ts',
|
|
44
|
+
// Same vi.hoisted pattern: Task 576's claude-auth refresh-lock test
|
|
45
|
+
// builds a tmpdir + creds path before the module's `../paths` mock fires.
|
|
46
|
+
'platform/ui/app/lib/__tests__/claude-auth-refresh-lock.test.ts',
|
|
44
47
|
])
|
|
45
48
|
|
|
46
49
|
const REQUIRE_CALL_RE = /\brequire\s*\(/
|
|
@@ -66,14 +66,12 @@ cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
|
|
|
66
66
|
{
|
|
67
67
|
"matcher": "Write",
|
|
68
68
|
"hooks": [
|
|
69
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" },
|
|
70
69
|
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
71
70
|
]
|
|
72
71
|
},
|
|
73
72
|
{
|
|
74
73
|
"matcher": "Edit",
|
|
75
74
|
"hooks": [
|
|
76
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" },
|
|
77
75
|
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
78
76
|
]
|
|
79
77
|
},
|
|
@@ -86,22 +84,9 @@ cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
|
|
|
86
84
|
{
|
|
87
85
|
"matcher": "Bash",
|
|
88
86
|
"hooks": [
|
|
89
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" },
|
|
90
87
|
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
91
88
|
]
|
|
92
89
|
},
|
|
93
|
-
{
|
|
94
|
-
"matcher": "mcp__plugin_memory_memory__memory-write",
|
|
95
|
-
"hooks": [
|
|
96
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" }
|
|
97
|
-
]
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
"matcher": "mcp__plugin_memory_memory__memory-update",
|
|
101
|
-
"hooks": [
|
|
102
|
-
{ "type": "command", "command": "bash $HOOKS_PATH/pre-tool-use.sh admin" }
|
|
103
|
-
]
|
|
104
|
-
},
|
|
105
90
|
{
|
|
106
91
|
"hooks": [
|
|
107
92
|
{ "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type AuthSnapshot = 'dead' | 'expired' | 'unknown';
|
|
2
|
+
export declare function credentialsPathForConfigDir(claudeConfigDir: string): string;
|
|
3
|
+
export declare function snapshotAuthForFailureReason(credentialsPath: string | undefined): AuthSnapshot;
|
|
4
|
+
//# sourceMappingURL=auth-snapshot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-snapshot.d.ts","sourceRoot":"","sources":["../src/auth-snapshot.ts"],"names":[],"mappings":"AAmBA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAA;AAEzD,wBAAgB,2BAA2B,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED,wBAAgB,4BAA4B,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,CAqB9F"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task 576 — auth-snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Read-only, no-dependency snapshot of the OAuth credentials file used
|
|
5
|
+
* by the http-server spawn-failure path to surface `reason=auth-refresh-failed`
|
|
6
|
+
* to the operator instead of the generic `pid-file-timeout`. Lives in
|
|
7
|
+
* claude-session-manager so the failure path stays in one tree; the
|
|
8
|
+
* authoritative refresh + lock logic continues to live in
|
|
9
|
+
* `platform/ui/app/lib/claude-auth.ts`.
|
|
10
|
+
*
|
|
11
|
+
* Returns one of:
|
|
12
|
+
* - `dead` — token expired AND no refresh token available
|
|
13
|
+
* - `expired` — token expired but a refresh token is on disk
|
|
14
|
+
* - `unknown` — token still valid, or the file is missing / unreadable
|
|
15
|
+
* (not auth's fault — let the original failure reason stand)
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { resolve } from 'node:path';
|
|
19
|
+
export function credentialsPathForConfigDir(claudeConfigDir) {
|
|
20
|
+
return resolve(claudeConfigDir, '.credentials.json');
|
|
21
|
+
}
|
|
22
|
+
export function snapshotAuthForFailureReason(credentialsPath) {
|
|
23
|
+
if (!credentialsPath)
|
|
24
|
+
return 'unknown';
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = readFileSync(credentialsPath, 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 'unknown';
|
|
31
|
+
}
|
|
32
|
+
let parsed;
|
|
33
|
+
try {
|
|
34
|
+
parsed = JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
const oauth = parsed.claudeAiOauth;
|
|
40
|
+
if (!oauth || typeof oauth !== 'object')
|
|
41
|
+
return 'unknown';
|
|
42
|
+
const expiresAt = oauth.expiresAt;
|
|
43
|
+
if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt))
|
|
44
|
+
return 'unknown';
|
|
45
|
+
if (expiresAt > Date.now())
|
|
46
|
+
return 'unknown'; // not the auth path's fault
|
|
47
|
+
const refreshToken = oauth.refreshToken;
|
|
48
|
+
return typeof refreshToken === 'string' && refreshToken.length > 0 ? 'expired' : 'dead';
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=auth-snapshot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-snapshot.js","sourceRoot":"","sources":["../src/auth-snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAInC,MAAM,UAAU,2BAA2B,CAAC,eAAuB;IACjE,OAAO,OAAO,CAAC,eAAe,EAAE,mBAAmB,CAAC,CAAA;AACtD,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,eAAmC;IAC9E,IAAI,CAAC,eAAe;QAAE,OAAO,SAAS,CAAA;IACtC,IAAI,GAAW,CAAA;IACf,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAA;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,IAAI,MAA+B,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAA;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,aAAoD,CAAA;IACzE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAA;IACzD,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,CAAA;IACjC,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAA;IAClF,IAAI,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,SAAS,CAAA,CAAC,4BAA4B;IACzE,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,CAAA;IACvC,OAAO,OAAO,YAAY,KAAK,QAAQ,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAA;AACzF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAM3B,OAAO,EAgBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAezB,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAM3B,OAAO,EAgBL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAezB,OAAO,KAAK,EAAE,SAAS,EAAc,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAgF9E,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAI1D,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E,OAAO,EAAE,SAAS,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,kBAAkB,EAAE,WAAW,CAAA;IAC/B,eAAe,EAAE,aAAa,CAAA;IAC9B;kFAC8E;IAC9E,cAAc,EAAE,cAAc,CAAA;CAC/B;AA4LD;;;kEAGkE;AAClE,MAAM,MAAM,OAAO,GAAG,IAAI,GAAG;IAC3B,2BAA2B,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACpD;;;+CAG2C;IAC3C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAChD,CAAA;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAs5CpD"}
|
|
@@ -31,6 +31,7 @@ import { readJsonlSession } from './jsonl-enumerator.js';
|
|
|
31
31
|
import { claudeStateRoot, projectSlugForCwd, jsonlPathForSessionId, sidecarPathForSessionId, permissionModeLogPathForSessionId, findExistingJsonlForSessionId, } from './jsonl-path.js';
|
|
32
32
|
import { basename, join } from 'node:path';
|
|
33
33
|
import { validateUserTitle } from './user-title-store.js';
|
|
34
|
+
import { credentialsPathForConfigDir, snapshotAuthForFailureReason } from './auth-snapshot.js';
|
|
34
35
|
const ROLES = ['admin', 'public'];
|
|
35
36
|
const CHANNELS = ['browser', 'whatsapp', 'telegram', 'webchat', 'email'];
|
|
36
37
|
// Task 382 — body.conversationNodeId shape gate. The value is the
|
|
@@ -672,7 +673,16 @@ export function buildHttpApp(deps) {
|
|
|
672
673
|
return c.json({ error: 'operator-slots-reserved', total: result.rejected.total, reserve: result.rejected.reserve, totalCap: result.rejected.totalCap }, 503);
|
|
673
674
|
}
|
|
674
675
|
timed(deps.logger, 'POST', '/spawn', 500, Date.now() - start);
|
|
675
|
-
|
|
676
|
+
// Task 576 — surface the auth-refresh-failed reason when the
|
|
677
|
+
// creds file says `dead` or `expired`. Without this the operator
|
|
678
|
+
// sees a generic `pid-file-timeout` for what is actually an
|
|
679
|
+
// OAuth-token failure and cannot recover via "Reconnect Claude".
|
|
680
|
+
const authSnap = snapshotAuthForFailureReason(credentialsPathForConfigDir(deps.claudeConfigDir));
|
|
681
|
+
const reason = authSnap === 'dead' || authSnap === 'expired' ? 'auth-refresh-failed' : result.reason;
|
|
682
|
+
if (reason === 'auth-refresh-failed') {
|
|
683
|
+
deps.logger(`[spawn-failed] reason=auth-refresh-failed authStatus=${authSnap} originalReason=${result.reason}`);
|
|
684
|
+
}
|
|
685
|
+
return c.json({ error: 'spawn-failed', reason, stderrTail: result.stderrTail, authStatus: authSnap }, 500);
|
|
676
686
|
}
|
|
677
687
|
timed(deps.logger, 'POST', '/spawn', 200, Date.now() - start);
|
|
678
688
|
const payload = buildSpawnReturnPayload(result.session.sessionId, result.session.pid, result.session.startedAt, result.session.url);
|
|
@@ -1487,7 +1497,14 @@ export function buildHttpApp(deps) {
|
|
|
1487
1497
|
deps.logger(`[sessions-rc-resume] spawn-failed sessionId=${sessionId ?? 'new'} name=${name ?? 'none'} err=${msg}`);
|
|
1488
1498
|
deps.logger(`[rc-spawn] op=spawn-failed unitToken=${unitToken} err=${JSON.stringify(msg)}`);
|
|
1489
1499
|
timed(deps.logger, 'POST', '/rc-spawn', 500, Date.now() - start);
|
|
1490
|
-
|
|
1500
|
+
// Task 576 — same auth-aware reason as /spawn so the resume path
|
|
1501
|
+
// surfaces auth death too, not only spawn-time failures.
|
|
1502
|
+
const authSnap = snapshotAuthForFailureReason(credentialsPathForConfigDir(deps.claudeConfigDir));
|
|
1503
|
+
const reason = authSnap === 'dead' || authSnap === 'expired' ? 'auth-refresh-failed' : msg;
|
|
1504
|
+
if (reason === 'auth-refresh-failed') {
|
|
1505
|
+
deps.logger(`[rc-spawn] op=auth-refresh-failed authStatus=${authSnap} unitToken=${unitToken}`);
|
|
1506
|
+
}
|
|
1507
|
+
return c.json({ error: msg, reason, authStatus: authSnap }, 500);
|
|
1491
1508
|
}
|
|
1492
1509
|
// Step 3 — PTY spawned. fd baseline at this instant; the fd-release
|
|
1493
1510
|
// line later in the lifecycle is read relative to this value.
|