@agent-marshal/marshal-prime-agent 0.48.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/package.json +48 -0
- package/src/daemon.ts +358 -0
- package/src/entities.ts +197 -0
- package/src/generated/AckMessages.ts +16 -0
- package/src/generated/AckMessagesArgs.ts +16 -0
- package/src/generated/AckMessagesResult.ts +12 -0
- package/src/generated/AutoSource.ts +9 -0
- package/src/generated/BroadcastMessage.ts +10 -0
- package/src/generated/BroadcastMessageArgs.ts +10 -0
- package/src/generated/BroadcastMessageResult.ts +25 -0
- package/src/generated/CancelSubscription.ts +6 -0
- package/src/generated/ChildEntities.ts +6 -0
- package/src/generated/ChildEntitiesAllTime.ts +6 -0
- package/src/generated/ClearClientWindbackTime.ts +7 -0
- package/src/generated/ClearClientWindbackTimeArgs.ts +3 -0
- package/src/generated/Client.ts +14 -0
- package/src/generated/ClientCount.ts +3 -0
- package/src/generated/ClientId.ts +3 -0
- package/src/generated/ClientStatus.ts +7 -0
- package/src/generated/ClientStatusOutput.ts +3 -0
- package/src/generated/CommandError.ts +3 -0
- package/src/generated/CommandResponse.ts +4 -0
- package/src/generated/CountAllClients.ts +3 -0
- package/src/generated/CountAllMessageReads.ts +3 -0
- package/src/generated/CountAllMessages.ts +3 -0
- package/src/generated/CountAllRoomMembers.ts +3 -0
- package/src/generated/CountAllRooms.ts +3 -0
- package/src/generated/CountAllServers.ts +3 -0
- package/src/generated/CountAllSessionNicknames.ts +3 -0
- package/src/generated/CountAllSessions.ts +3 -0
- package/src/generated/CountClients.ts +4 -0
- package/src/generated/CountMessageReads.ts +4 -0
- package/src/generated/CountMessages.ts +4 -0
- package/src/generated/CountRoomMembers.ts +4 -0
- package/src/generated/CountRooms.ts +4 -0
- package/src/generated/CountServers.ts +4 -0
- package/src/generated/CountSessionNicknames.ts +4 -0
- package/src/generated/CountSessions.ts +4 -0
- package/src/generated/DeleteClient.ts +7 -0
- package/src/generated/DeleteClientArgs.ts +4 -0
- package/src/generated/DeleteClientResult.ts +6 -0
- package/src/generated/DeleteClients.ts +7 -0
- package/src/generated/DeleteClientsArgs.ts +4 -0
- package/src/generated/DeleteClientsResult.ts +6 -0
- package/src/generated/DeleteMessage.ts +7 -0
- package/src/generated/DeleteMessageArgs.ts +4 -0
- package/src/generated/DeleteMessageRead.ts +7 -0
- package/src/generated/DeleteMessageReadArgs.ts +4 -0
- package/src/generated/DeleteMessageReadResult.ts +6 -0
- package/src/generated/DeleteMessageReads.ts +7 -0
- package/src/generated/DeleteMessageReadsArgs.ts +4 -0
- package/src/generated/DeleteMessageReadsResult.ts +6 -0
- package/src/generated/DeleteMessageResult.ts +6 -0
- package/src/generated/DeleteMessages.ts +7 -0
- package/src/generated/DeleteMessagesArgs.ts +4 -0
- package/src/generated/DeleteMessagesResult.ts +6 -0
- package/src/generated/DeleteRoom.ts +7 -0
- package/src/generated/DeleteRoomArgs.ts +4 -0
- package/src/generated/DeleteRoomMember.ts +7 -0
- package/src/generated/DeleteRoomMemberArgs.ts +4 -0
- package/src/generated/DeleteRoomMemberResult.ts +6 -0
- package/src/generated/DeleteRoomMembers.ts +7 -0
- package/src/generated/DeleteRoomMembersArgs.ts +4 -0
- package/src/generated/DeleteRoomMembersResult.ts +6 -0
- package/src/generated/DeleteRoomResult.ts +6 -0
- package/src/generated/DeleteRooms.ts +7 -0
- package/src/generated/DeleteRoomsArgs.ts +4 -0
- package/src/generated/DeleteRoomsResult.ts +6 -0
- package/src/generated/DeleteServer.ts +7 -0
- package/src/generated/DeleteServerArgs.ts +4 -0
- package/src/generated/DeleteServerResult.ts +6 -0
- package/src/generated/DeleteServers.ts +7 -0
- package/src/generated/DeleteServersArgs.ts +4 -0
- package/src/generated/DeleteServersResult.ts +6 -0
- package/src/generated/DeleteSession.ts +7 -0
- package/src/generated/DeleteSessionArgs.ts +4 -0
- package/src/generated/DeleteSessionNickname.ts +7 -0
- package/src/generated/DeleteSessionNicknameArgs.ts +4 -0
- package/src/generated/DeleteSessionNicknameResult.ts +6 -0
- package/src/generated/DeleteSessionNicknames.ts +7 -0
- package/src/generated/DeleteSessionNicknamesArgs.ts +4 -0
- package/src/generated/DeleteSessionNicknamesResult.ts +6 -0
- package/src/generated/DeleteSessionResult.ts +6 -0
- package/src/generated/DeleteSessions.ts +7 -0
- package/src/generated/DeleteSessionsArgs.ts +4 -0
- package/src/generated/DeleteSessionsResult.ts +6 -0
- package/src/generated/DeliveredRecipient.ts +4 -0
- package/src/generated/EntitySearch.ts +21 -0
- package/src/generated/EntitySearchResult.ts +10 -0
- package/src/generated/EntitySnapshotDifference.ts +6 -0
- package/src/generated/EntitySnapshotDifferenceData.ts +7 -0
- package/src/generated/EntityTreeExport.ts +27 -0
- package/src/generated/EventContainer.ts +7 -0
- package/src/generated/EventOptions.ts +11 -0
- package/src/generated/EventsForTransaction.ts +6 -0
- package/src/generated/ExportEntityTree.ts +24 -0
- package/src/generated/ExportedEntity.ts +15 -0
- package/src/generated/FailedRecipient.ts +4 -0
- package/src/generated/FullChildEntities.ts +6 -0
- package/src/generated/GetAllClients.ts +3 -0
- package/src/generated/GetAllMessageReads.ts +3 -0
- package/src/generated/GetAllMessages.ts +3 -0
- package/src/generated/GetAllRoomMembers.ts +3 -0
- package/src/generated/GetAllRooms.ts +3 -0
- package/src/generated/GetAllServers.ts +3 -0
- package/src/generated/GetAllSessionNicknames.ts +3 -0
- package/src/generated/GetAllSessions.ts +3 -0
- package/src/generated/GetClientById.ts +4 -0
- package/src/generated/GetClientsByIds.ts +4 -0
- package/src/generated/GetClientsByQuery.ts +4 -0
- package/src/generated/GetConnectedServer.ts +3 -0
- package/src/generated/GetItemsByTypeAndIds.ts +14 -0
- package/src/generated/GetMessageById.ts +4 -0
- package/src/generated/GetMessageReadById.ts +4 -0
- package/src/generated/GetMessageReadsByIds.ts +4 -0
- package/src/generated/GetMessageReadsByQuery.ts +4 -0
- package/src/generated/GetMessagesByIds.ts +4 -0
- package/src/generated/GetMessagesByQuery.ts +4 -0
- package/src/generated/GetPeerServers.ts +3 -0
- package/src/generated/GetPersistHealth.ts +8 -0
- package/src/generated/GetRoomById.ts +4 -0
- package/src/generated/GetRoomMemberById.ts +4 -0
- package/src/generated/GetRoomMembersByIds.ts +4 -0
- package/src/generated/GetRoomMembersByQuery.ts +4 -0
- package/src/generated/GetRoomsByIds.ts +4 -0
- package/src/generated/GetRoomsByQuery.ts +4 -0
- package/src/generated/GetServerById.ts +4 -0
- package/src/generated/GetServersByIds.ts +4 -0
- package/src/generated/GetServersByQuery.ts +4 -0
- package/src/generated/GetSessionById.ts +4 -0
- package/src/generated/GetSessionNicknameById.ts +4 -0
- package/src/generated/GetSessionNicknamesByIds.ts +4 -0
- package/src/generated/GetSessionNicknamesByQuery.ts +4 -0
- package/src/generated/GetSessionsByIds.ts +4 -0
- package/src/generated/GetSessionsByQuery.ts +4 -0
- package/src/generated/HostInfo.ts +21 -0
- package/src/generated/ImportItems.ts +19 -0
- package/src/generated/ImportItemsArgs.ts +13 -0
- package/src/generated/ItemStub.ts +7 -0
- package/src/generated/JoinRoom.ts +19 -0
- package/src/generated/JoinRoomArgs.ts +19 -0
- package/src/generated/JoinRoomResult.ts +14 -0
- package/src/generated/LeaveRoom.ts +14 -0
- package/src/generated/LeaveRoomArgs.ts +14 -0
- package/src/generated/LeaveRoomResult.ts +9 -0
- package/src/generated/LivePushStatus.ts +3 -0
- package/src/generated/LogLevel.ts +6 -0
- package/src/generated/Loggers.ts +6 -0
- package/src/generated/MEvent.ts +5 -0
- package/src/generated/MEventType.ts +3 -0
- package/src/generated/Message.ts +60 -0
- package/src/generated/MessageCount.ts +3 -0
- package/src/generated/MessageId.ts +3 -0
- package/src/generated/MessageRead.ts +10 -0
- package/src/generated/MessageReadCount.ts +3 -0
- package/src/generated/MessageReadId.ts +3 -0
- package/src/generated/MessageView.ts +12 -0
- package/src/generated/MykoMessage.ts +19 -0
- package/src/generated/NotifyChannel.ts +10 -0
- package/src/generated/NotifyChannelArgs.ts +4 -0
- package/src/generated/PartialClient.ts +14 -0
- package/src/generated/PartialMessage.ts +60 -0
- package/src/generated/PartialMessageRead.ts +10 -0
- package/src/generated/PartialRoom.ts +23 -0
- package/src/generated/PartialRoomMember.ts +13 -0
- package/src/generated/PartialServer.ts +4 -0
- package/src/generated/PartialSession.ts +130 -0
- package/src/generated/PartialSessionNickname.ts +9 -0
- package/src/generated/PeerAlive.ts +7 -0
- package/src/generated/PersistHealthStatus.ts +34 -0
- package/src/generated/PingData.ts +14 -0
- package/src/generated/QueryError.ts +3 -0
- package/src/generated/QueryWindow.ts +3 -0
- package/src/generated/QueryWindowUpdate.ts +4 -0
- package/src/generated/ReadMessages.ts +48 -0
- package/src/generated/ReadMessagesArgs.ts +48 -0
- package/src/generated/ReadMessagesResult.ts +9 -0
- package/src/generated/ReportError.ts +3 -0
- package/src/generated/ReportResponse.ts +4 -0
- package/src/generated/Room.ts +23 -0
- package/src/generated/RoomCount.ts +3 -0
- package/src/generated/RoomId.ts +3 -0
- package/src/generated/RoomKind.ts +9 -0
- package/src/generated/RoomMember.ts +13 -0
- package/src/generated/RoomMemberCount.ts +3 -0
- package/src/generated/RoomMemberId.ts +3 -0
- package/src/generated/SearchServer.ts +8 -0
- package/src/generated/SearchServerResult.ts +4 -0
- package/src/generated/SendMessage.ts +12 -0
- package/src/generated/SendMessageArgs.ts +12 -0
- package/src/generated/SendMessageResult.ts +33 -0
- package/src/generated/Server.ts +4 -0
- package/src/generated/ServerCount.ts +3 -0
- package/src/generated/ServerId.ts +3 -0
- package/src/generated/ServerLogLevel.ts +6 -0
- package/src/generated/ServerStats.ts +27 -0
- package/src/generated/ServerStatsOutput.ts +20 -0
- package/src/generated/Session.ts +130 -0
- package/src/generated/SessionCount.ts +3 -0
- package/src/generated/SessionId.ts +3 -0
- package/src/generated/SessionNickname.ts +9 -0
- package/src/generated/SessionNicknameCount.ts +3 -0
- package/src/generated/SessionNicknameId.ts +3 -0
- package/src/generated/SetClientWindbackTime.ts +11 -0
- package/src/generated/SetClientWindbackTimeArgs.ts +7 -0
- package/src/generated/SetLogLevel.ts +7 -0
- package/src/generated/SetLogLevelArgs.ts +4 -0
- package/src/generated/SetRoomDescription.ts +7 -0
- package/src/generated/SetRoomDescriptionArgs.ts +4 -0
- package/src/generated/SetRoomName.ts +7 -0
- package/src/generated/SetRoomNameArgs.ts +4 -0
- package/src/generated/SetSessionActivity.ts +7 -0
- package/src/generated/SetSessionActivityArgs.ts +4 -0
- package/src/generated/SetSessionCurrentTask.ts +7 -0
- package/src/generated/SetSessionCurrentTaskArgs.ts +4 -0
- package/src/generated/SetSessionGitBranch.ts +7 -0
- package/src/generated/SetSessionGitBranchArgs.ts +4 -0
- package/src/generated/SetSessionLastActivityAt.ts +7 -0
- package/src/generated/SetSessionLastActivityAtArgs.ts +4 -0
- package/src/generated/SetSessionLastTool.ts +7 -0
- package/src/generated/SetSessionLastToolArgs.ts +4 -0
- package/src/generated/SetSessionLastToolAt.ts +7 -0
- package/src/generated/SetSessionLastToolAtArgs.ts +4 -0
- package/src/generated/SetSessionSessionName.ts +7 -0
- package/src/generated/SetSessionSessionNameArgs.ts +4 -0
- package/src/generated/StartProfile.ts +10 -0
- package/src/generated/StartProfileArgs.ts +7 -0
- package/src/generated/StopProfile.ts +6 -0
- package/src/generated/StopProfileArgs.ts +3 -0
- package/src/generated/StopProfileOutput.ts +14 -0
- package/src/generated/ViewError.ts +3 -0
- package/src/generated/ViewWindowUpdate.ts +4 -0
- package/src/generated/WakeStatus.ts +3 -0
- package/src/generated/WindbackStatus.ts +3 -0
- package/src/generated/WindbackStatusOutput.ts +11 -0
- package/src/generated/WrappedCommand.ts +4 -0
- package/src/generated/WrappedItem.ts +7 -0
- package/src/generated/WrappedQuery.ts +5 -0
- package/src/generated/WrappedReport.ts +4 -0
- package/src/generated/WrappedView.ts +5 -0
- package/src/generated/index.ts +1060 -0
- package/src/generated/serde_json/JsonValue.ts +3 -0
- package/src/identity.ts +61 -0
- package/src/index.ts +589 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Marshal for Prime Agent
|
|
2
|
+
|
|
3
|
+
`@agent-marshal/marshal-prime-agent` is the native Prime Agent bridge for
|
|
4
|
+
[marshal](https://github.com/ignition-is-go/marshal). It is separate from the
|
|
5
|
+
`marshal-pi` package and targets Prime Agent's conversation lifecycle and canonical
|
|
6
|
+
session identities.
|
|
7
|
+
|
|
8
|
+
The extension runs inside each Prime Agent conversation. It:
|
|
9
|
+
|
|
10
|
+
- registers the conversation on the Marshal roster using Prime Agent's session ID;
|
|
11
|
+
- exposes native `marshal_*` tools to the model;
|
|
12
|
+
- subscribes to Marshal's live notification stream; and
|
|
13
|
+
- injects incoming direct messages with
|
|
14
|
+
`pi.sendMessage(..., { deliverAs: "steer", triggerTurn: true })`.
|
|
15
|
+
|
|
16
|
+
A direct message therefore appears in the transcript immediately and starts a turn
|
|
17
|
+
when the recipient is idle. While the agent is working, it steers the active run at
|
|
18
|
+
the next tool boundary. A durable inbox pull before each turn recovers messages
|
|
19
|
+
received while disconnected.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Start a reachable daemon, then install the Prime Agent package:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cargo install marshal-daemon
|
|
27
|
+
marshal-daemon &
|
|
28
|
+
prime-agent package install npm:@agent-marshal/marshal-prime-agent
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Restart Prime Agent after installation. No MCP server or wrapper is required.
|
|
32
|
+
For a remote daemon:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
export MARSHAL_DAEMON_ADDRESS=ws://marshal.example.net:6155
|
|
36
|
+
prime-agent
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The default is `ws://localhost:6155`. `MARSHAL_OPERATOR` optionally overrides the
|
|
40
|
+
operator name shown on the roster.
|
|
41
|
+
|
|
42
|
+
For development:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cargo run -p marshal-entities --no-default-features --features codegen \
|
|
46
|
+
--bin typegen -- plugins/marshal-prime-agent/src/generated
|
|
47
|
+
prime-agent --extension ./plugins/marshal-prime-agent/src/index.ts
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Verify
|
|
51
|
+
|
|
52
|
+
Run `/marshal-status` inside Prime Agent or use `marshal_whoami`. Send a direct
|
|
53
|
+
message from another Marshal session to its nickname or session ID. It appears as:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
new message from <nickname>: <body>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Peer content is untrusted input. The extension adds that warning to the system
|
|
60
|
+
prompt and directs replies through `marshal_send_message`. Room broadcasts remain
|
|
61
|
+
ambient unless the recipient is directly mentioned.
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
65
|
+
- `marshal_whoami`
|
|
66
|
+
- `marshal_roster`
|
|
67
|
+
- `marshal_messages`
|
|
68
|
+
- `marshal_send_message`
|
|
69
|
+
- `marshal_broadcast`
|
|
70
|
+
- `marshal_join_room`
|
|
71
|
+
- `marshal_leave_room`
|
|
72
|
+
- `marshal_set_status`
|
|
73
|
+
- `marshal_ack`
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agent-marshal/marshal-prime-agent",
|
|
3
|
+
"version": "0.48.4",
|
|
4
|
+
"description": "Prime Agent extension that connects live conversations to the marshal coordination daemon with native tools and real-time steering delivery of sibling-agent messages.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT OR Apache-2.0",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"main": "./src/index.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"prime-agent-package",
|
|
17
|
+
"prime-agent",
|
|
18
|
+
"marshal",
|
|
19
|
+
"agent-coordination"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@myko/core": "^4.24.4"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
26
|
+
"typebox": "*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"typescript": "^5.3.3"
|
|
30
|
+
},
|
|
31
|
+
"pi": {
|
|
32
|
+
"extensions": [
|
|
33
|
+
"./src/index.ts"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"flux": {
|
|
37
|
+
"bridges": [
|
|
38
|
+
"cargo:marshal-entities"
|
|
39
|
+
],
|
|
40
|
+
"tasks": [
|
|
41
|
+
"gen",
|
|
42
|
+
"publish"
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"gen": "cargo run -p marshal-entities --no-default-features --features codegen --bin typegen -- src/generated"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/daemon.ts
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// MarshalDaemon — the pi-side equivalent of the Rust marshal-shim's daemon
|
|
2
|
+
// wiring, built on the @myko/core TypeScript client (official myko protocol).
|
|
3
|
+
//
|
|
4
|
+
// Responsibilities (1:1 with the shim's main.rs, minus the Claude-specific
|
|
5
|
+
// session-id archaeology and the MCP stdio transport):
|
|
6
|
+
// - hold one autoreconnecting WS to the marshal daemon,
|
|
7
|
+
// - SET a `Session` roster row per live pi session and keep its liveness
|
|
8
|
+
// fresh (5s cadence, like the shim's roster-publish loop),
|
|
9
|
+
// - re-SET every tracked session on reconnect (the daemon's roster is
|
|
10
|
+
// in-memory and lost on restart),
|
|
11
|
+
// - run the write commands (send/broadcast/join/leave/status/ack) and the
|
|
12
|
+
// per-turn inbox pull, all carrying `asSession` for caller identity,
|
|
13
|
+
// - surface inbound `NotifyChannel` pushes to a callback (→ turn injection).
|
|
14
|
+
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { ConnectionStatus, MykoClient } from "@myko/core";
|
|
17
|
+
import type { MEvent } from "@myko/core";
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
ackMessages,
|
|
21
|
+
broadcastMessage,
|
|
22
|
+
getAllSessions,
|
|
23
|
+
getAllSessionNicknames,
|
|
24
|
+
joinRoom,
|
|
25
|
+
leaveRoom,
|
|
26
|
+
NOTIFY_CHANNEL_COMMAND_ID,
|
|
27
|
+
readMessages,
|
|
28
|
+
sendMessage,
|
|
29
|
+
setLastActivityAt,
|
|
30
|
+
setSessionStatus,
|
|
31
|
+
type BroadcastMessageResult,
|
|
32
|
+
type JoinRoomResult,
|
|
33
|
+
type LeaveRoomResult,
|
|
34
|
+
type MarshalCommand,
|
|
35
|
+
type MarshalQuery,
|
|
36
|
+
type MessageView,
|
|
37
|
+
type NotifyChannelMeta,
|
|
38
|
+
type ReadMessagesResult,
|
|
39
|
+
type SendMessageResult,
|
|
40
|
+
type SessionItem,
|
|
41
|
+
type SessionNicknameItem,
|
|
42
|
+
} from "./entities.ts";
|
|
43
|
+
import type { Identity } from "./identity.ts";
|
|
44
|
+
|
|
45
|
+
const LIVENESS_INTERVAL_MS = 5_000;
|
|
46
|
+
const INBOX_PULL_LIMIT = 20;
|
|
47
|
+
const SOURCE_ID = "marshal-prime-agent";
|
|
48
|
+
|
|
49
|
+
/** myko's CellServer serves its WebSocket at the `/myko` path. */
|
|
50
|
+
export function withMykoPath(addr: string): string {
|
|
51
|
+
try {
|
|
52
|
+
const u = new URL(addr);
|
|
53
|
+
if (u.pathname === "" || u.pathname === "/") u.pathname = "/myko";
|
|
54
|
+
return u.toString().replace(/\/$/, "");
|
|
55
|
+
} catch {
|
|
56
|
+
return /\/myko\/?$/.test(addr) ? addr : `${addr.replace(/\/+$/, "")}/myko`;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface DaemonConfig {
|
|
61
|
+
address: string;
|
|
62
|
+
cwd: string;
|
|
63
|
+
identity: Identity;
|
|
64
|
+
log?: (msg: string) => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type NotifyHandler = (meta: NotifyChannelMeta) => void;
|
|
68
|
+
|
|
69
|
+
export class MarshalDaemon {
|
|
70
|
+
private readonly client = new MykoClient();
|
|
71
|
+
private readonly cfg: DaemonConfig;
|
|
72
|
+
private readonly log: (msg: string) => void;
|
|
73
|
+
|
|
74
|
+
private readonly sessions = new Map<string, SessionItem>();
|
|
75
|
+
private readonly communicated = new Map<string, Set<string>>();
|
|
76
|
+
private lastSessionId: string | undefined;
|
|
77
|
+
|
|
78
|
+
private roster: SessionItem[] = [];
|
|
79
|
+
private nicknames: SessionNicknameItem[] = [];
|
|
80
|
+
|
|
81
|
+
private readonly draining = new Map<string, Promise<string | null>>();
|
|
82
|
+
private notifyHandler: NotifyHandler | null = null;
|
|
83
|
+
private livenessTimer: ReturnType<typeof setInterval> | null = null;
|
|
84
|
+
private started = false;
|
|
85
|
+
private connected = false;
|
|
86
|
+
private rosterChangedHandler: (() => void) | null = null;
|
|
87
|
+
private connectionChangedHandler: ((connected: boolean) => void) | null = null;
|
|
88
|
+
|
|
89
|
+
constructor(cfg: DaemonConfig) {
|
|
90
|
+
this.cfg = cfg;
|
|
91
|
+
this.log = cfg.log ?? (() => {});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Public API ─────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
start(): void {
|
|
97
|
+
if (this.started) return;
|
|
98
|
+
this.started = true;
|
|
99
|
+
|
|
100
|
+
this.client.connectionStatus$.subscribe((status) => {
|
|
101
|
+
if (status === ConnectionStatus.Connected) {
|
|
102
|
+
this.connected = true;
|
|
103
|
+
for (const item of this.sessions.values()) this.emitSet(item);
|
|
104
|
+
this.connectionChangedHandler?.(true);
|
|
105
|
+
} else if (status === ConnectionStatus.Disconnected) {
|
|
106
|
+
this.connected = false;
|
|
107
|
+
this.connectionChangedHandler?.(false);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.client.commandIncoming$.subscribe((msg) => {
|
|
112
|
+
const data = (msg as { data?: { commandId?: string; command?: unknown } }).data;
|
|
113
|
+
if (!data || data.commandId !== NOTIFY_CHANNEL_COMMAND_ID) return;
|
|
114
|
+
const command = (data.command ?? {}) as { meta?: NotifyChannelMeta };
|
|
115
|
+
this.notifyHandler?.(command.meta ?? {});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
this.watch(getAllSessions()).subscribe((items) => {
|
|
119
|
+
this.roster = items;
|
|
120
|
+
this.rosterChangedHandler?.();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
this.watch(getAllSessionNicknames()).subscribe((items) => {
|
|
124
|
+
this.nicknames = items;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.client.setAddress(withMykoPath(this.cfg.address));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
stop(): void {
|
|
131
|
+
for (const item of this.sessions.values()) this.emitDel(item);
|
|
132
|
+
this.sessions.clear();
|
|
133
|
+
if (this.livenessTimer) { clearInterval(this.livenessTimer); this.livenessTimer = null; }
|
|
134
|
+
this.client.disconnect();
|
|
135
|
+
this.started = false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
onNotify(handler: NotifyHandler): void { this.notifyHandler = handler; }
|
|
139
|
+
onRosterChanged(handler: () => void): void { this.rosterChangedHandler = handler; }
|
|
140
|
+
onConnectionChanged(handler: (connected: boolean) => void): void { this.connectionChangedHandler = handler; }
|
|
141
|
+
isConnected(): boolean { return this.connected; }
|
|
142
|
+
|
|
143
|
+
// ── Session management ─────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
registerSession(sessionId: string): void {
|
|
146
|
+
this.lastSessionId = sessionId;
|
|
147
|
+
const existing = this.sessions.get(sessionId);
|
|
148
|
+
const now = Date.now() as unknown as bigint;
|
|
149
|
+
const item: SessionItem = existing ?? {
|
|
150
|
+
id: sessionId,
|
|
151
|
+
pid: process.pid,
|
|
152
|
+
cwd: this.cfg.cwd,
|
|
153
|
+
connectedAt: now,
|
|
154
|
+
operator: this.cfg.identity.operator ?? null,
|
|
155
|
+
host: this.cfg.identity.host ?? null,
|
|
156
|
+
gitBranch: this.cfg.identity.gitBranch ?? null,
|
|
157
|
+
project: this.cfg.identity.project ?? null,
|
|
158
|
+
// Prime Agent can render custom messages and trigger/steer turns through
|
|
159
|
+
// its extension API, so advertise live-channel capability explicitly.
|
|
160
|
+
channelsEnabled: true,
|
|
161
|
+
kind: "prime-agent",
|
|
162
|
+
} as SessionItem;
|
|
163
|
+
(item as any).lastActivityAt = now;
|
|
164
|
+
this.sessions.set(sessionId, item);
|
|
165
|
+
if (this.connected) this.emitSet(item);
|
|
166
|
+
this.ensureLivenessTimer();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
deregisterSession(sessionId: string): void {
|
|
170
|
+
const item = this.sessions.get(sessionId);
|
|
171
|
+
if (!item) return;
|
|
172
|
+
this.sessions.delete(sessionId);
|
|
173
|
+
this.emitDel(item);
|
|
174
|
+
if (this.sessions.size === 0 && this.livenessTimer) {
|
|
175
|
+
clearInterval(this.livenessTimer);
|
|
176
|
+
this.livenessTimer = null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Write tools ────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
sendMessage(asSession: string, to: string, body: string): Promise<SendMessageResult> {
|
|
183
|
+
this.recordCommunication(asSession, to);
|
|
184
|
+
return this.send(sendMessage(asSession, to, body));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
recordCommunication(sessionId: string, peerSessionId: string | undefined): void {
|
|
188
|
+
if (!peerSessionId || peerSessionId === sessionId) return;
|
|
189
|
+
const peers = this.communicated.get(sessionId) ?? new Set<string>();
|
|
190
|
+
peers.add(peerSessionId);
|
|
191
|
+
this.communicated.set(sessionId, peers);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
broadcast(asSession: string, room: string, body: string): Promise<BroadcastMessageResult> {
|
|
195
|
+
return this.send(broadcastMessage(asSession, room, body));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
joinRoom(asSession: string, name: string, description?: string): Promise<JoinRoomResult> {
|
|
199
|
+
return this.send(joinRoom(asSession, name, description));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
leaveRoom(asSession: string, room: string): Promise<LeaveRoomResult> {
|
|
203
|
+
return this.send(leaveRoom(asSession, room));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async setStatus(sessionId: string, text: string): Promise<void> {
|
|
207
|
+
const item = this.sessions.get(sessionId);
|
|
208
|
+
if (item) (item as any).currentTask = text;
|
|
209
|
+
await this.send(setSessionStatus(sessionId, text));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── Reads ──────────────────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
async roster_snapshot(): Promise<SessionItem[]> {
|
|
215
|
+
if (this.roster.length > 0) return this.roster;
|
|
216
|
+
return new Promise<SessionItem[]>((resolve) => {
|
|
217
|
+
let done = false;
|
|
218
|
+
const sub = this.watch(getAllSessions()).subscribe((items) => {
|
|
219
|
+
if (done) return;
|
|
220
|
+
done = true;
|
|
221
|
+
resolve(items);
|
|
222
|
+
queueMicrotask(() => sub.unsubscribe());
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
rosterSync(): SessionItem[] { return this.roster; }
|
|
228
|
+
|
|
229
|
+
async readHistory(asSession: string, opts: { room?: string; since?: number; limit?: number }): Promise<MessageView[]> {
|
|
230
|
+
const result = await this.send(readMessages({
|
|
231
|
+
asSession,
|
|
232
|
+
room: opts.room,
|
|
233
|
+
inbox: opts.room ? false : true,
|
|
234
|
+
sent: false,
|
|
235
|
+
unread: false,
|
|
236
|
+
since: opts.since,
|
|
237
|
+
limit: opts.limit ?? 50,
|
|
238
|
+
}));
|
|
239
|
+
const messages = result.messages ?? [];
|
|
240
|
+
for (const m of messages) this.recordCommunication(asSession, m.fromSessionId);
|
|
241
|
+
return messages;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async ackMessages(asSession: string, messageIds: string[]): Promise<number> {
|
|
245
|
+
await this.send(ackMessages(asSession, messageIds));
|
|
246
|
+
return messageIds.length;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
formatMessageLine(m: MessageView): string {
|
|
250
|
+
return `- [${m.messageId}] ${this.senderLabel(m.fromSessionId)}: ${m.body}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
drainInbox(sessionId: string): Promise<string | null> {
|
|
254
|
+
const prev = this.draining.get(sessionId) ?? Promise.resolve<string | null>(null);
|
|
255
|
+
const next = prev.catch(() => null).then(() => this.drainInboxInner(sessionId));
|
|
256
|
+
this.draining.set(sessionId, next);
|
|
257
|
+
void next.finally(() => { if (this.draining.get(sessionId) === next) this.draining.delete(sessionId); });
|
|
258
|
+
return next;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
nicknameFor(sessionId: string): string {
|
|
262
|
+
return this.nicknames.find((n) => (n as any).id === sessionId)?.nickname ?? sessionId;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
currentNickname(): string | undefined {
|
|
266
|
+
const sid = this.lastSessionId ?? (this.sessions.keys().next().value as string | undefined);
|
|
267
|
+
if (!sid) return undefined;
|
|
268
|
+
return this.nicknameFor(sid);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ── Internals ──────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
private async drainInboxInner(sessionId: string): Promise<string | null> {
|
|
274
|
+
let result: ReadMessagesResult | undefined;
|
|
275
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
276
|
+
try {
|
|
277
|
+
result = await this.send(readMessages({
|
|
278
|
+
asSession: sessionId,
|
|
279
|
+
toSession: sessionId,
|
|
280
|
+
inbox: false,
|
|
281
|
+
sent: false,
|
|
282
|
+
unread: true,
|
|
283
|
+
limit: INBOX_PULL_LIMIT,
|
|
284
|
+
}));
|
|
285
|
+
} catch {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
if (result.messages && result.messages.length > 0) break;
|
|
289
|
+
if (attempt < 2) await new Promise((r) => setTimeout(r, 100 * (attempt + 1)));
|
|
290
|
+
}
|
|
291
|
+
if (!result?.messages?.length) return null;
|
|
292
|
+
|
|
293
|
+
const block = this.renderInbox(result.messages);
|
|
294
|
+
try {
|
|
295
|
+
await this.send(ackMessages(sessionId, result.messages.map((m) => m.messageId)));
|
|
296
|
+
} catch { this.log("inbox ack failed"); }
|
|
297
|
+
return block;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private renderInbox(messages: MessageView[]): string {
|
|
301
|
+
if (messages.length === 1) {
|
|
302
|
+
const m = messages[0];
|
|
303
|
+
return `new message from ${this.senderLabel(m.fromSessionId)}: ${m.body}`;
|
|
304
|
+
}
|
|
305
|
+
const lines = [`${messages.length} new messages from sibling agents:`];
|
|
306
|
+
for (const m of messages) lines.push(`- ${this.senderLabel(m.fromSessionId)}: ${m.body}`);
|
|
307
|
+
return lines.join("\n");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private senderLabel(sessionId: string): string {
|
|
311
|
+
const nick = this.nicknameFor(sessionId);
|
|
312
|
+
const s = this.roster.find((r) => (r as any).id === sessionId);
|
|
313
|
+
if (!s) return nick;
|
|
314
|
+
const ctx = [s.operator, s.host?.name].filter(Boolean).join("@");
|
|
315
|
+
return ctx ? `${nick} (${ctx})` : nick;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private ensureLivenessTimer(): void {
|
|
319
|
+
if (this.livenessTimer) return;
|
|
320
|
+
this.livenessTimer = setInterval(() => {
|
|
321
|
+
const now = Date.now();
|
|
322
|
+
for (const [id, item] of this.sessions) {
|
|
323
|
+
(item as any).lastActivityAt = now;
|
|
324
|
+
void this.send(setLastActivityAt(id, now)).catch(() => {});
|
|
325
|
+
}
|
|
326
|
+
}, LIVENESS_INTERVAL_MS);
|
|
327
|
+
if (typeof (this.livenessTimer as any).unref === "function") {
|
|
328
|
+
(this.livenessTimer as any).unref();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
private emitSet(item: SessionItem): void {
|
|
333
|
+
this.client.sendEvent(this.event(item, "SET"));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private emitDel(item: SessionItem): void {
|
|
337
|
+
this.client.sendEvent(this.event(item, "DEL"));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private event(item: SessionItem, changeType: "SET" | "DEL"): MEvent {
|
|
341
|
+
return {
|
|
342
|
+
item: item as unknown as Record<string, unknown>,
|
|
343
|
+
changeType,
|
|
344
|
+
itemType: "Session",
|
|
345
|
+
createdAt: new Date().toISOString(),
|
|
346
|
+
tx: randomUUID(),
|
|
347
|
+
sourceId: SOURCE_ID,
|
|
348
|
+
} as unknown as MEvent;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private send<R>(command: MarshalCommand<R>): Promise<R> {
|
|
352
|
+
return this.client.sendCommand(command as never) as Promise<R>;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private watch<I>(query: MarshalQuery<I>) {
|
|
356
|
+
return this.client.watchQuery(query as never) as unknown as import("rxjs").Observable<I[]>;
|
|
357
|
+
}
|
|
358
|
+
}
|
package/src/entities.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// Marshal wire shapes for the pi extension.
|
|
2
|
+
//
|
|
3
|
+
// ALL marshal types come from the generated bindings in `./generated/`, which
|
|
4
|
+
// are produced from the canonical Rust `marshal-entities` definitions by
|
|
5
|
+
// `scripts/generate-pi-types.sh`. Regenerate them whenever the Rust entities
|
|
6
|
+
// change so the wire contract can't drift.
|
|
7
|
+
//
|
|
8
|
+
// What stays here:
|
|
9
|
+
// - `MarshalCommand` / `MarshalQuery` — generic myko envelopes (not marshal-specific).
|
|
10
|
+
// - builder functions — thin wrappers that return `{ commandId, command }`.
|
|
11
|
+
// - `NotifyChannelMeta` — documents the runtime shape of the freeform JSON
|
|
12
|
+
// `meta` field the daemon pushes (the Rust `serde_json::Value` has no type).
|
|
13
|
+
// - `NOTIFY_CHANNEL_COMMAND_ID` — the push command id constant.
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
AckMessagesResult,
|
|
17
|
+
BroadcastMessageResult,
|
|
18
|
+
JoinRoomResult,
|
|
19
|
+
LeaveRoomResult,
|
|
20
|
+
MessageView,
|
|
21
|
+
ReadMessagesArgs,
|
|
22
|
+
ReadMessagesResult,
|
|
23
|
+
Room,
|
|
24
|
+
RoomMember,
|
|
25
|
+
SendMessageResult,
|
|
26
|
+
Session,
|
|
27
|
+
SessionNickname,
|
|
28
|
+
} from "./generated/index.ts";
|
|
29
|
+
|
|
30
|
+
// ── Re-exported generated types ──────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
export type {
|
|
33
|
+
AckMessagesResult,
|
|
34
|
+
BroadcastMessageResult,
|
|
35
|
+
HostInfo,
|
|
36
|
+
JoinRoomResult,
|
|
37
|
+
LeaveRoomResult,
|
|
38
|
+
MessageView,
|
|
39
|
+
ReadMessagesResult,
|
|
40
|
+
Room,
|
|
41
|
+
RoomMember,
|
|
42
|
+
SendMessageResult,
|
|
43
|
+
Session,
|
|
44
|
+
SessionNickname,
|
|
45
|
+
SessionId,
|
|
46
|
+
RoomId,
|
|
47
|
+
MessageId,
|
|
48
|
+
} from "./generated/index.ts";
|
|
49
|
+
|
|
50
|
+
/** The roster-row payload this extension SETs. The generated `Session` type
|
|
51
|
+
* brands timestamps as `bigint` (ts-rs maps Rust `i64` that way); at runtime
|
|
52
|
+
* marshal serializes them as cbor numbers, so the daemon casts `Date.now()`
|
|
53
|
+
* values at construction. */
|
|
54
|
+
export type SessionItem = Session;
|
|
55
|
+
|
|
56
|
+
/** The daemon-assigned handle, read from the `SessionNickname` store. */
|
|
57
|
+
export type SessionNicknameItem = SessionNickname;
|
|
58
|
+
|
|
59
|
+
/** A room entry from `GetAllRooms`. */
|
|
60
|
+
export type RoomItem = Room;
|
|
61
|
+
|
|
62
|
+
/** A room membership from `GetAllRoomMembers`. */
|
|
63
|
+
export type RoomMemberItem = RoomMember;
|
|
64
|
+
|
|
65
|
+
// ── Server→client push meta ──────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/** The `NotifyChannel.meta` the daemon dispatches to the connected client
|
|
68
|
+
* when a peer sends a message. The Rust source defines it as `serde_json::Value`
|
|
69
|
+
* (freeform), so this is a convenience type, not a generated marshal one. */
|
|
70
|
+
export interface NotifyChannelMeta {
|
|
71
|
+
source?: string;
|
|
72
|
+
kind?: string;
|
|
73
|
+
message_id?: string;
|
|
74
|
+
from_session?: string;
|
|
75
|
+
from_nickname?: string;
|
|
76
|
+
to_session?: string;
|
|
77
|
+
to_operator?: string | null;
|
|
78
|
+
body?: string;
|
|
79
|
+
body_truncated?: boolean;
|
|
80
|
+
sent_at?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const NOTIFY_CHANNEL_COMMAND_ID = "NotifyChannel";
|
|
84
|
+
|
|
85
|
+
// ── Envelope types ───────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
/** A myko command envelope as `MykoClient.sendCommand` expects it. */
|
|
88
|
+
export interface MarshalCommand<_Result = unknown> {
|
|
89
|
+
readonly commandId: string;
|
|
90
|
+
readonly command: Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A myko query envelope as `MykoClient.watchQuery` expects it. */
|
|
94
|
+
export interface MarshalQuery<_Item = unknown> {
|
|
95
|
+
readonly queryId: string;
|
|
96
|
+
readonly queryItemType: string;
|
|
97
|
+
readonly query: Record<string, unknown>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Write command builders ───────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
export function sendMessage(
|
|
103
|
+
asSession: string,
|
|
104
|
+
toSessionId: string,
|
|
105
|
+
body: string,
|
|
106
|
+
): MarshalCommand<SendMessageResult> {
|
|
107
|
+
return { commandId: "SendMessage", command: { toSessionId, body, asSession } };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function broadcastMessage(
|
|
111
|
+
asSession: string,
|
|
112
|
+
toRoomId: string,
|
|
113
|
+
body: string,
|
|
114
|
+
): MarshalCommand<BroadcastMessageResult> {
|
|
115
|
+
return { commandId: "BroadcastMessage", command: { toRoomId, body, asSession } };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function joinRoom(
|
|
119
|
+
asSession: string,
|
|
120
|
+
name: string,
|
|
121
|
+
description?: string,
|
|
122
|
+
): MarshalCommand<JoinRoomResult> {
|
|
123
|
+
return { commandId: "JoinRoom", command: stripUndefined({ name, description, asSession }) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function leaveRoom(
|
|
127
|
+
asSession: string,
|
|
128
|
+
room: string,
|
|
129
|
+
): MarshalCommand<LeaveRoomResult> {
|
|
130
|
+
return { commandId: "LeaveRoom", command: { room, asSession } };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function ackMessages(
|
|
134
|
+
asSession: string,
|
|
135
|
+
messageIds: string[],
|
|
136
|
+
): MarshalCommand<AckMessagesResult> {
|
|
137
|
+
return { commandId: "AckMessages", command: { messageIds, asSession } };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** `set_status` maps to the generated setter for `Session.current_task`
|
|
141
|
+
* (`Set{Item}{Field}` — myko/macros/src/item.rs). Payload is `{ id, currentTask }`. */
|
|
142
|
+
export function setSessionStatus(
|
|
143
|
+
sessionId: string,
|
|
144
|
+
currentTask: string | null,
|
|
145
|
+
): MarshalCommand<void> {
|
|
146
|
+
return { commandId: "SetSessionCurrentTask", command: { id: sessionId, currentTask } };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Liveness bump — the generated setter for `Session.last_activity_at`. Mirrors
|
|
150
|
+
* the Rust shim's 5s roster-publish loop so the daemon sweeper doesn't reap us. */
|
|
151
|
+
export function setLastActivityAt(
|
|
152
|
+
sessionId: string,
|
|
153
|
+
lastActivityAt: number,
|
|
154
|
+
): MarshalCommand<void> {
|
|
155
|
+
return { commandId: "SetSessionLastActivityAt", command: { id: sessionId, lastActivityAt } };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function readMessages(args: ReadMessagesArgs): MarshalCommand<ReadMessagesResult> {
|
|
159
|
+
const command = stripUndefined({
|
|
160
|
+
asSession: args.asSession,
|
|
161
|
+
inbox: args.inbox ?? false,
|
|
162
|
+
sent: args.sent ?? false,
|
|
163
|
+
unread: args.unread ?? false,
|
|
164
|
+
room: args.room,
|
|
165
|
+
from: args.from,
|
|
166
|
+
toSession: args.toSession,
|
|
167
|
+
since: args.since,
|
|
168
|
+
limit: args.limit,
|
|
169
|
+
});
|
|
170
|
+
return { commandId: "ReadMessages", command };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Read query builders ──────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
export function getAllSessions(): MarshalQuery<Session> {
|
|
176
|
+
return { queryId: "GetAllSessions", queryItemType: "Session", query: {} };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function getAllRooms(): MarshalQuery<Room> {
|
|
180
|
+
return { queryId: "GetAllRooms", queryItemType: "Room", query: {} };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function getAllRoomMembers(): MarshalQuery<RoomMember> {
|
|
184
|
+
return { queryId: "GetAllRoomMembers", queryItemType: "RoomMember", query: {} };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function getAllSessionNicknames(): MarshalQuery<SessionNickname> {
|
|
188
|
+
return { queryId: "GetAllSessionNicknames", queryItemType: "SessionNickname", query: {} };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function stripUndefined(o: Record<string, unknown>): Record<string, unknown> {
|
|
192
|
+
const out: Record<string, unknown> = {};
|
|
193
|
+
for (const [k, v] of Object.entries(o)) {
|
|
194
|
+
if (v !== undefined) out[k] = v;
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
|
2
|
+
import type { MessageId } from "./MessageId";
|
|
3
|
+
import type { SessionId } from "./SessionId";
|
|
4
|
+
|
|
5
|
+
export type AckMessages = {
|
|
6
|
+
/**
|
|
7
|
+
* Messages to mark read. Order doesn't matter; missing/unknown
|
|
8
|
+
* ids are silently skipped (idempotent — re-acking is fine).
|
|
9
|
+
*/
|
|
10
|
+
messageIds: Array<MessageId>,
|
|
11
|
+
/**
|
|
12
|
+
* Self-identified caller for connectionless paths (HTTP-MCP agents,
|
|
13
|
+
* the daemon's `/hook/*` handlers acking on a session's behalf).
|
|
14
|
+
* WS shim callers omit it. See `resolve_caller`.
|
|
15
|
+
*/
|
|
16
|
+
asSession?: SessionId | null, };
|