@rubytech/create-maxy-code 0.1.275 → 0.1.277
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/plugins/admin/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +1 -4
- package/payload/platform/plugins/admin/mcp/dist/index.js +0 -8
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -3
- package/payload/platform/plugins/docs/references/admin-identity-gate.md +77 -81
- package/payload/platform/plugins/docs/references/admin-ui.md +2 -2
- package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
- package/payload/platform/scripts/setup-account.sh +0 -5
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +0 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +5 -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 +52 -10
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +8 -17
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- 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 +4 -5
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts +8 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js +13 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-mcp.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/wa-channel-store.d.ts +29 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-store.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-store.js +124 -0
- package/payload/platform/services/claude-session-manager/dist/wa-channel-store.js.map +1 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +23 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +26 -0
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +41 -14
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/index.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts +2 -0
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js +1 -1
- package/payload/premium-plugins/writer-craft/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/premium-plugins/writer-craft/mcp/src/index.ts +1 -1
- package/payload/premium-plugins/writer-craft/mcp/src/tools/voice-distil-profile.ts +3 -1
- package/payload/server/{chunk-W4EM7RK4.js → chunk-QHD5TKLQ.js} +2 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +551 -405
- package/payload/platform/plugins/admin/hooks/__tests__/pin-identity-gate.test.sh +0 -96
- package/payload/platform/plugins/admin/hooks/pin-identity-gate.sh +0 -146
|
@@ -1,81 +1,77 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
`
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
surfaces it.
|
|
79
|
-
|
|
80
|
-
Diagnostic path: `journalctl --user -u <brand>-admin | grep -E '\[admin-identity\]'`,
|
|
81
|
-
then filter by `sessionId` for one session's full lifeline.
|
|
1
|
+
# Admin identity model
|
|
2
|
+
|
|
3
|
+
Admin identity is carried by the surface a session arrives on. It is never gated
|
|
4
|
+
by a per-session step, and nothing a non-interactive channel cannot satisfy ever
|
|
5
|
+
blocks a tool.
|
|
6
|
+
|
|
7
|
+
The PIN identity gate (Task 619) — a universal PreToolUse hook that denied every
|
|
8
|
+
tool until the operator submitted a PIN — was retired in Task 681. It deadlocked
|
|
9
|
+
non-interactive surfaces: a WhatsApp channel session can never enter a PIN, so the
|
|
10
|
+
channel reply tool was permanently unreachable. The hook, its `admin-identity-authenticate`
|
|
11
|
+
submit tool, the `/api/admin/identity/validate` loopback endpoint, and the standing
|
|
12
|
+
identity audit were all removed. The shared PIN primitive (`pin-validator.ts`) stays
|
|
13
|
+
— the legacy browser login (`POST /api/admin/session`) still uses it.
|
|
14
|
+
|
|
15
|
+
## How identity is carried, per surface
|
|
16
|
+
|
|
17
|
+
**Channels (WhatsApp, future channels).** The channel binding authenticates the
|
|
18
|
+
operator. An allowlisted sender phone in `account.json` `adminPhones` makes the
|
|
19
|
+
inbound session the `admin` role (`access-control.ts`), and the channel-PTY bridge
|
|
20
|
+
keys the admin session per sender (`bridge.ts`). No PIN, no prompt — the sender's
|
|
21
|
+
own number is the credential.
|
|
22
|
+
|
|
23
|
+
**Loopback (claude.ai/code).** The operator chats through the per-account `claude rc`
|
|
24
|
+
daemon over loopback. There is no inbound identifier on this surface, so the session
|
|
25
|
+
carries the single boot-time `USER_ID` (the owner, read from `users.json[0]` at
|
|
26
|
+
daemon start). When more than one admin shares the account, the admin agent identifies
|
|
27
|
+
which person it is speaking with conversationally — a convenience that loads the right
|
|
28
|
+
profile and history. This is stated in the admin `IDENTITY.md`: identification is
|
|
29
|
+
"purely as a convenience... never a gate and never blocks any tool"; single-admin
|
|
30
|
+
sessions skip it.
|
|
31
|
+
|
|
32
|
+
## Accepted boundary
|
|
33
|
+
|
|
34
|
+
On a channel (WhatsApp today), the inbound sender phone is bound to a person's
|
|
35
|
+
`userId` via `users.json` (`UserEntry.phone`), confirmed against the account's
|
|
36
|
+
`admins[]`. The channel admin session carries that `userId` — graph writes and
|
|
37
|
+
the `${USER_ID}` MCP env attribute to the actual sender (Task 682). An unbound
|
|
38
|
+
sender (the owner's self-phone, or an `adminPhones` entry with no `users.json`
|
|
39
|
+
phone) falls back to the boot-time owner `userId`.
|
|
40
|
+
|
|
41
|
+
A WhatsApp admin whose inbound arrives as an unresolved LID (a cold mapping
|
|
42
|
+
cache) resolves to their own `userId` via a runtime LID cache
|
|
43
|
+
(`whatsapp-lid-map.json`), populated the first time their LID resolves to their
|
|
44
|
+
phone (Task 686). A LID that has never resolved has no cache entry and still
|
|
45
|
+
falls back to the owner `userId` — visible as the `op=admin-identity …
|
|
46
|
+
userId=owner-fallback` log line.
|
|
47
|
+
|
|
48
|
+
On loopback (claude.ai/code), multiple admins resolve to the right *profile*
|
|
49
|
+
conversationally but share the owner's `userId` at the env/MCP layer.
|
|
50
|
+
Deterministic per-admin `userId` on loopback is deliberately out of scope:
|
|
51
|
+
making it deterministic again means a per-session identity step, which is exactly
|
|
52
|
+
the deadlock Task 681 removed.
|
|
53
|
+
|
|
54
|
+
## Boot-time binding drift gate (Task 687)
|
|
55
|
+
|
|
56
|
+
The channel-admin binding is reassembled at read time from three independently
|
|
57
|
+
operator-maintained sources: `adminPhones` (role allowlist), `admins[]`
|
|
58
|
+
(membership), and `users.json` `UserEntry.phone` (the phone→userId binding). If
|
|
59
|
+
they disagree, `resolveAdminUserId` returns null and the session silently runs
|
|
60
|
+
under the owner `userId` — the fallback named in the boundary above, but here as
|
|
61
|
+
a *misconfiguration*, not an intentional unbound sender.
|
|
62
|
+
|
|
63
|
+
On every server start the platform replays the resolution chain for each
|
|
64
|
+
account's `adminPhones` entry and logs one line per entry that would silently
|
|
65
|
+
fall to the owner `userId`:
|
|
66
|
+
|
|
67
|
+
[admin-identity] binding-drift accountId=<id> phone=<p> reason=no-users-binding
|
|
68
|
+
[admin-identity] binding-drift accountId=<id> phone=<p> reason=userid-not-admin userId=<u>
|
|
69
|
+
|
|
70
|
+
- `no-users-binding` — no `users.json` entry's `phone` matches this admin phone,
|
|
71
|
+
so it can never resolve to a userId. Add the phone to the person's `users.json`
|
|
72
|
+
entry.
|
|
73
|
+
- `userid-not-admin` — the phone is bound to `userId`, but that userId is not in
|
|
74
|
+
this account's `admins[]`. Add the userId to `admins[]`.
|
|
75
|
+
|
|
76
|
+
The gate is informational: it never blocks boot, and steady-state boots with
|
|
77
|
+
consistent bindings emit nothing.
|
|
@@ -70,7 +70,7 @@ Task 500 — **admin session management moved entirely to claude's own interface
|
|
|
70
70
|
|
|
71
71
|
| Mount | Purpose |
|
|
72
72
|
|---|---|
|
|
73
|
-
| `/sidebar-artefacts` | Lists every
|
|
73
|
+
| `/sidebar-artefacts` | Lists the sidebar Artefacts rows: every `:FileArtifact` under this account's tree (`relativePath STARTS WITH 'accounts/'` — all file types, excluding the separate `uploads/<id>/` subtree) plus this account's IDENTITY / SOUL / KNOWLEDGE / specialist templates (Task 684). |
|
|
74
74
|
| `/sidebar-artefact-content` | Reads a single artefact's bytes for the artefact pane. |
|
|
75
75
|
| `/sidebar-artefact-save` | Persists an artefact edit. |
|
|
76
76
|
| `/attachment` | Per-attachment binary fetch (images, PDFs, etc.). |
|
|
@@ -217,7 +217,7 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
217
217
|
| Nav rows (Chat / People / Agents / Projects / Tasks / Artefacts) | People, Agents, Tasks open the artefact-pane Graph filtered to the matching label. Chat selects the active conversation. Artefacts swaps the list to editable documents. | `graph-search.ts`, `graph-subgraph.ts`, `sidebar-artefacts.ts` |
|
|
218
218
|
| Sessions list (Active / Archived / All) | Live row store driven by SSE; manual reconcile button on the segmented control re-fetches the full id set. | `/claude-sessions/events`, `/claude-sessions` |
|
|
219
219
|
| Conversations row hover actions | Inline rename, archive, delete, JSONL view / download per row. The historical `.conversations-modal` CSS block exists in `globals.css` but is no longer mounted from any TSX — Sidebar.tsx now owns every per-row affordance directly. | `claude-sessions.ts` |
|
|
220
|
-
| Artefacts list | Lists every
|
|
220
|
+
| Artefacts list | Lists every `:FileArtifact` under this account's tree (`relativePath STARTS WITH 'accounts/'`, all file types, excluding the `uploads/<id>/` subtree — Task 684) plus this account's IDENTITY / SOUL / KNOWLEDGE / specialist templates. Click downloads the row's backing file (`downloadPath` → `GET /api/admin/files/download`) so the operator opens it in their local app; rows whose file is outside `DATA_ROOT` (bundled-fallback templates) show a "can't be downloaded" pill. The in-app artefact pane is dead pending removal (Task 518). | `sidebar-artefacts.ts`, `files.ts` |
|
|
221
221
|
| System-stats widget | CPU / RAM widget at the foot of the sidebar. | `system-stats.ts` (see above) |
|
|
222
222
|
| Footer | Operator avatar, name, role, and the actions popover. | `session.ts` |
|
|
223
223
|
|
|
@@ -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 684's sidebar-artefacts test pre-builds an
|
|
45
|
+
// empty tmpdir for ACCOUNTS_DIR/PLATFORM_ROOT before the module's mocks fire.
|
|
46
|
+
'platform/ui/server/routes/admin/__tests__/sidebar-artefacts.test.ts',
|
|
44
47
|
// Same vi.hoisted pattern: Task 576's claude-auth refresh-lock test
|
|
45
48
|
// builds a tmpdir + creds path before the module's `../paths` mock fires.
|
|
46
49
|
'platform/ui/app/lib/__tests__/claude-auth-refresh-lock.test.ts',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical-tool-names.generated.d.ts","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"AAQA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAiB7C,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,
|
|
1
|
+
{"version":3,"file":"canonical-tool-names.generated.d.ts","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"AAQA,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAiB7C,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAgLtD,CAAA"}
|
package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js
CHANGED
|
@@ -33,7 +33,6 @@ export const CANONICAL_MAXY_TOOL_NAMES = [
|
|
|
33
33
|
"mcp__plugin_admin_admin__action-pending",
|
|
34
34
|
"mcp__plugin_admin_admin__action-reject",
|
|
35
35
|
"mcp__plugin_admin_admin__admin-add",
|
|
36
|
-
"mcp__plugin_admin_admin__admin-identity-authenticate",
|
|
37
36
|
"mcp__plugin_admin_admin__admin-list",
|
|
38
37
|
"mcp__plugin_admin_admin__admin-remove",
|
|
39
38
|
"mcp__plugin_admin_admin__admin-update-pin",
|
package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canonical-tool-names.generated.js","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,kEAAkE;AAClE,yDAAyD;AACzD,kFAAkF;AAClF,EAAE;AACF,yEAAyE;AACzE,6DAA6D;AAE7D,+EAA+E;AAC/E,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,OAAO;IACP,KAAK;IACL,SAAS;IACT,UAAU;IACV,OAAO;IACP,OAAO;IACP,cAAc;IACd,QAAQ;IACR,SAAS;IACT,WAAW;IACX,YAAY;IACZ,UAAU;IACV,SAAS;IACT,UAAU;IACV,MAAM;IACN,WAAW;CACZ,CAAA;AAED,gEAAgE;AAChE,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC1D,yCAAyC;IACzC,yCAAyC;IACzC,yCAAyC;IACzC,sCAAsC;IACtC,yCAAyC;IACzC,wCAAwC;IACxC,oCAAoC;IACpC,
|
|
1
|
+
{"version":3,"file":"canonical-tool-names.generated.js","sourceRoot":"","sources":["../src/canonical-tool-names.generated.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,7 @@ import type { FsWatcher } from './fs-watcher.js';
|
|
|
4
4
|
import type { RateLimiter } from './spawn-rate-limiter.js';
|
|
5
5
|
import type { AuditRegistry } from './public-tool-audit.js';
|
|
6
6
|
import { type UserTitleStore } from './user-title-store.js';
|
|
7
|
+
import { WaChannelStore } from './wa-channel-store.js';
|
|
7
8
|
export declare const SESSION_ID_PATTERN: RegExp;
|
|
8
9
|
export declare const RC_SPAWN_URL_WAIT_DEFAULT_MS = 60000;
|
|
9
10
|
export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'> {
|
|
@@ -19,6 +20,10 @@ export interface HttpDeps extends Omit<SpawnDeps, 'onSessionReady' | 'watcher'>
|
|
|
19
20
|
/** Task 253 — operator-stamped title override store. Reads merge into
|
|
20
21
|
* every displayName resolution site; the /:sessionId/rename route writes. */
|
|
21
22
|
userTitleStore: UserTitleStore;
|
|
23
|
+
/** Task 683 — per-session WhatsApp channel binding. Gateway `/rc-spawn`
|
|
24
|
+
* write-throughs it; every resume path reads it back to re-apply the
|
|
25
|
+
* channel argv so `reply` is present on every live PTY for the session. */
|
|
26
|
+
waChannelStore: WaChannelStore;
|
|
22
27
|
/** Task 648 — override the `/rc-spawn` pid/bind poll bound. Production
|
|
23
28
|
* omits this and gets RC_SPAWN_URL_WAIT_DEFAULT_MS (60s); tests inject a
|
|
24
29
|
* short value to exercise the bind-timeout response without a 60s wait. */
|
|
@@ -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;AAS3B,OAAO,EAeL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAkBzB,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;AAS3B,OAAO,EAeL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAA;AAkBzB,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;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAgFtD,eAAO,MAAM,kBAAkB,QAA2B,CAAA;AAS1D,eAAO,MAAM,4BAA4B,QAAS,CAAA;AAIlD,MAAM,WAAW,QAAS,SAAQ,IAAI,CAAC,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC7E;;qEAEiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,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;IAC9B;;gFAE4E;IAC5E,cAAc,EAAE,cAAc,CAAA;IAC9B;;gFAE4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AA+MD;;;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;AAmBD,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAsuDpD"}
|
|
@@ -23,7 +23,7 @@ import { Hono } from 'hono';
|
|
|
23
23
|
import { stream } from 'hono/streaming';
|
|
24
24
|
import { existsSync, statSync, createReadStream, watchFile, unwatchFile, rmSync, mkdirSync, renameSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
25
|
import { tmpdir } from 'node:os';
|
|
26
|
-
import { buildWaChannelMcpServers,
|
|
26
|
+
import { buildWaChannelMcpServers, buildWaChannelArgv, waChannelMcpConfigPath, isDevChannelPrompt } from './wa-channel-mcp.js';
|
|
27
27
|
import { randomUUID } from 'node:crypto';
|
|
28
28
|
import { execFileSync } from 'node:child_process';
|
|
29
29
|
import { buildRcChildEnv } from './rc-daemon.js';
|
|
@@ -1295,6 +1295,10 @@ export function buildHttpApp(deps) {
|
|
|
1295
1295
|
// logs its own failure and reads still serve from the in-memory map
|
|
1296
1296
|
// until next boot.
|
|
1297
1297
|
deps.userTitleStore.remove(sessionId);
|
|
1298
|
+
// Task 683 — drop the channel binding too so wa-channel-bindings.json
|
|
1299
|
+
// doesn't accrete orphans and a recreated session can't inherit a stale
|
|
1300
|
+
// binding. Best-effort; the store logs its own failure.
|
|
1301
|
+
deps.waChannelStore.remove(sessionId);
|
|
1298
1302
|
deps.logger(`purge sessionId=${sessionId.slice(0, 8)} bytes=${bytes}`);
|
|
1299
1303
|
timed(deps.logger, 'DELETE', `/${id}`, 204, Date.now() - start);
|
|
1300
1304
|
return c.body(null, 204);
|
|
@@ -1549,6 +1553,13 @@ export function buildHttpApp(deps) {
|
|
|
1549
1553
|
const personId = typeof body.personId === 'string' && body.personId.length > 0 && body.personId.length <= 128
|
|
1550
1554
|
? body.personId
|
|
1551
1555
|
: undefined;
|
|
1556
|
+
// Task 682 — per-session userId override. Server-resolved by the UI from the
|
|
1557
|
+
// authoritative users.json/account.json (never message content) and shape-
|
|
1558
|
+
// validated here the same way as personId. Absent for owner self-phone and
|
|
1559
|
+
// any unbound sender, where the boot-time deps.userId (owner) is used.
|
|
1560
|
+
const overrideUserId = typeof body.userId === 'string' && body.userId.length > 0 && body.userId.length <= 128
|
|
1561
|
+
? body.userId
|
|
1562
|
+
: undefined;
|
|
1552
1563
|
// Task 626 — one-shot admin jobs (LinkedIn ingest, public session-end
|
|
1553
1564
|
// review) carry their prompt as the session's first turn, the same
|
|
1554
1565
|
// trailing-positional mechanism /public-spawn uses, and close after that
|
|
@@ -1595,20 +1606,41 @@ export function buildHttpApp(deps) {
|
|
|
1595
1606
|
// development channel. Inbound WhatsApp then arrives as a `<channel
|
|
1596
1607
|
// source="whatsapp">` event and the agent replies via the channel `reply`
|
|
1597
1608
|
// tool — no keystroke injection. Pushed before the trailing positional.
|
|
1598
|
-
|
|
1609
|
+
// Task 683 — resolve the effective binding. The gateway `/rc-spawn` supplies
|
|
1610
|
+
// waChannel and we write it through to the store; any later resume (operator
|
|
1611
|
+
// / PIN / manager) omits it, so we read the binding back. Either way the same
|
|
1612
|
+
// per-session --mcp-config + development-channels argv is injected, so
|
|
1613
|
+
// `mcp__whatsapp-channel__reply` is present on every live PTY for the session.
|
|
1614
|
+
let effectiveWaChannel;
|
|
1615
|
+
if (sessionId) {
|
|
1616
|
+
if (waChannel) {
|
|
1617
|
+
effectiveWaChannel = waChannel;
|
|
1618
|
+
deps.waChannelStore.set(sessionId, waChannel);
|
|
1619
|
+
}
|
|
1620
|
+
else {
|
|
1621
|
+
const stored = deps.waChannelStore.get(sessionId);
|
|
1622
|
+
if (stored) {
|
|
1623
|
+
effectiveWaChannel = { senderId: stored.senderId, gatewayUrl: stored.gatewayUrl, serverPath: stored.serverPath };
|
|
1624
|
+
deps.logger(`[rc-spawn] op=wa-channel-rebind sessionId=${sessionId.slice(0, 8)} senderId=${stored.senderId} source=store`);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
let waPath = null;
|
|
1629
|
+
if (effectiveWaChannel && sessionId) {
|
|
1599
1630
|
const waServers = buildWaChannelMcpServers({
|
|
1600
1631
|
sessionId,
|
|
1601
|
-
senderId:
|
|
1602
|
-
gatewayUrl:
|
|
1603
|
-
serverPath:
|
|
1632
|
+
senderId: effectiveWaChannel.senderId,
|
|
1633
|
+
gatewayUrl: effectiveWaChannel.gatewayUrl,
|
|
1634
|
+
serverPath: effectiveWaChannel.serverPath,
|
|
1604
1635
|
});
|
|
1605
|
-
|
|
1636
|
+
waPath = waChannelMcpConfigPath(sessionId, tmpdir());
|
|
1606
1637
|
try {
|
|
1607
1638
|
writeFileSync(waPath, JSON.stringify({ mcpServers: waServers }, null, 2), 'utf8');
|
|
1608
|
-
argv.push(
|
|
1609
|
-
deps.logger(`[rc-spawn] op=wa-channel senderId=${
|
|
1639
|
+
argv.push(...buildWaChannelArgv(waPath));
|
|
1640
|
+
deps.logger(`[rc-spawn] op=wa-channel senderId=${effectiveWaChannel.senderId} gateway=${effectiveWaChannel.gatewayUrl} path=${waPath}`);
|
|
1610
1641
|
}
|
|
1611
1642
|
catch (err) {
|
|
1643
|
+
waPath = null;
|
|
1612
1644
|
deps.logger(`[rc-spawn] op=wa-channel-write-failed err=${JSON.stringify(err instanceof Error ? err.message : String(err))}`);
|
|
1613
1645
|
}
|
|
1614
1646
|
}
|
|
@@ -1622,7 +1654,7 @@ export function buildHttpApp(deps) {
|
|
|
1622
1654
|
// so the child's MCP stderr-tee writes its readable per-server log.
|
|
1623
1655
|
const env = buildRcChildEnv({
|
|
1624
1656
|
accountId: deps.accountId,
|
|
1625
|
-
userId: deps.userId,
|
|
1657
|
+
userId: overrideUserId ?? deps.userId,
|
|
1626
1658
|
sessionId: sessionId ?? undefined,
|
|
1627
1659
|
uiPort: process.env.MAXY_UI_INTERNAL_PORT ?? '',
|
|
1628
1660
|
platformRoot: deps.platformRoot,
|
|
@@ -1690,6 +1722,16 @@ export function buildHttpApp(deps) {
|
|
|
1690
1722
|
pid: pty.pid,
|
|
1691
1723
|
managerOpenFds: openFdCount(),
|
|
1692
1724
|
});
|
|
1725
|
+
// Task 683 — standing invariant. A bound session whose live PTY was spawned
|
|
1726
|
+
// without the channel mcp-config is the silent-revert state (the reply tool
|
|
1727
|
+
// is absent, the agent falls back to whatsapp-send). The resume read-back
|
|
1728
|
+
// makes the resume-without-binding case impossible, so this now fires only
|
|
1729
|
+
// when the mcp-config write itself failed (waPath reset to null) — it
|
|
1730
|
+
// verifies the post-condition (PTY actually carries the channel) rather than
|
|
1731
|
+
// the intent to spawn it.
|
|
1732
|
+
if (sessionId && deps.waChannelStore.get(sessionId) && waPath === null) {
|
|
1733
|
+
deps.logger(`[rc-spawn] op=channel-binding-lost sessionId=${sessionId} pid=${pty.pid} reason=resume-without-mcp-config`);
|
|
1734
|
+
}
|
|
1693
1735
|
// Tracker registration — closes the leak gap the Task 552 path opened.
|
|
1694
1736
|
// For fresh spawns without a caller-supplied sessionId we register the
|
|
1695
1737
|
// tracker under the unitToken so the entry has a stable key; the
|
|
@@ -1751,7 +1793,7 @@ export function buildHttpApp(deps) {
|
|
|
1751
1793
|
const BIND_BUF_TAIL = 8 * 1024;
|
|
1752
1794
|
pty.onData((data) => {
|
|
1753
1795
|
stdoutBytes += data.length;
|
|
1754
|
-
if (
|
|
1796
|
+
if (effectiveWaChannel && !devChannelConfirmed) {
|
|
1755
1797
|
devChannelScan += data;
|
|
1756
1798
|
if (devChannelScan.length > 4096)
|
|
1757
1799
|
devChannelScan = devChannelScan.slice(devChannelScan.length - 4096);
|