@anhlp/alp-protocol 0.0.1-beta.3 → 0.0.1-beta.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/conformance/cases.json +1 -1
- package/dist/connection.d.ts +4 -0
- package/dist/contract-v1.d.ts +4867 -1197
- package/dist/contract-v1.js +1 -1
- package/dist/events.d.ts +41 -0
- package/dist/index.d.ts +1 -0
- package/dist/rpc.d.ts +651 -11
- package/dist/schedule.d.ts +43 -0
- package/dist/schedule.js +0 -0
- package/dist/session.d.ts +142 -2
- package/dist/wire.d.ts +112 -0
- package/dist/wire.js +78 -3
- package/fixtures/v1/cases.json +3247 -0
- package/package.json +1 -1
- package/schema/ipc-v1.json +953 -17
- package/schema/methods-v1.json +4854 -933
- package/src/connection.ts +4 -0
- package/src/contract-v1.js +1 -1
- package/src/events.ts +28 -2
- package/src/index.ts +1 -0
- package/src/rpc.ts +171 -12
- package/src/schedule.ts +43 -0
- package/src/session.ts +120 -4
- package/src/wire.js +128 -2
package/src/events.ts
CHANGED
|
@@ -6,7 +6,13 @@ import type { RuntimeKind, MailEvent, Pin } from './dto.js';
|
|
|
6
6
|
export type AlpError = { message: string; code?: string };
|
|
7
7
|
|
|
8
8
|
export type TimelineItem =
|
|
9
|
-
| {
|
|
9
|
+
| {
|
|
10
|
+
kind: 'user_message'; id: string; text: string; clientMessageId?: string; /** How many images the message carried; their data is not replayed. */ images?: number;
|
|
11
|
+
/** Files attached to the message (capability `attachments`, §12.6): ids, display names, types and sizes, never a host path. */
|
|
12
|
+
attachments?: Array<{ attachmentId: string; name: string; mimeType: string; size: number }>;
|
|
13
|
+
/** Who wrote it: the user, or ALP itself (mail, a brief, a resume); absent when the runtime cannot tell. Only the user's words name a root (ALPD §15). */
|
|
14
|
+
origin?: TurnOrigin;
|
|
15
|
+
}
|
|
10
16
|
| { kind: 'assistant_message'; id: string; text: string }
|
|
11
17
|
/** Something about ALP as a whole the user should see, such as a usage limit or a pause (ALPD §29). */
|
|
12
18
|
| { kind: 'notice'; id: string; level: 'info' | 'warning' | 'error'; text: string }
|
|
@@ -99,6 +105,11 @@ export type SessionSnapshot = {
|
|
|
99
105
|
instructionsSha?: string;
|
|
100
106
|
/** The session's runtime takes images in session.prompt (capability `prompt.images`); absent means it does not. */
|
|
101
107
|
promptImages?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* How the session's runtime can use files attached in session.prompt (§12.6): `file.path` (its tools
|
|
110
|
+
* read a staged read-only copy), `document.native`, `text.extract`. Absent: it takes no attachment parts.
|
|
111
|
+
*/
|
|
112
|
+
promptAttachments?: Array<'file.path' | 'document.native' | 'text.extract'>;
|
|
102
113
|
/** The latest usage the session's runtime reported (ALPD §57); absent before its first report. */
|
|
103
114
|
usage?: SessionUsage;
|
|
104
115
|
};
|
|
@@ -116,6 +127,13 @@ export type AssignmentSnapshot = {
|
|
|
116
127
|
/** runtime: a turn the native runtime started by itself, such as Claude after a background task ended. */
|
|
117
128
|
export type TurnOrigin = 'user' | 'wake' | 'assignment' | 'runtime';
|
|
118
129
|
|
|
130
|
+
/**
|
|
131
|
+
* A root's turn that ended while nobody looked (Paseo's attention): `finished` for a completed turn,
|
|
132
|
+
* `error` for a failed one. alpd keeps it for every client until one acknowledges it or the root
|
|
133
|
+
* starts another turn (docs/protocol/alp-json-rpc-v1.md §12.2).
|
|
134
|
+
*/
|
|
135
|
+
export type TurnAttention = { reason: 'finished' | 'error'; at: string; turnId?: string; message?: string };
|
|
136
|
+
|
|
119
137
|
/** A question an agent asked the user with alp_ask to: "user". */
|
|
120
138
|
export type UserQuestion = {
|
|
121
139
|
id: string;
|
|
@@ -162,7 +180,15 @@ export type AlpEvent =
|
|
|
162
180
|
| { type: 'question.resolved'; questionId: string; outcome: 'answered' | 'dismissed' | 'timeout' | 'canceled'; answer?: string }
|
|
163
181
|
/** Emitted on the session that pinned it (plans/reference/ALPD.md §18). */
|
|
164
182
|
| { type: 'pin'; pin: Pin }
|
|
165
|
-
| { type: 'unpin'; pinId: string; reason: 'unpinned' | 'session_ended' }
|
|
183
|
+
| { type: 'unpin'; pinId: string; reason: 'unpinned' | 'session_ended' }
|
|
184
|
+
/** A root's attention set, or cleared when absent: sent only to `question.watch { attention: true }` connections, never kept. */
|
|
185
|
+
| { type: 'attention'; attention?: TurnAttention }
|
|
186
|
+
/**
|
|
187
|
+
* The root's conversation was rewound (capability `session.rewind`, §12.7): history pages and cursors of
|
|
188
|
+
* the earlier generation are stale. Sent to attached connections only, never kept: drop the cached
|
|
189
|
+
* history, fetch a new tail and attach again with this generation.
|
|
190
|
+
*/
|
|
191
|
+
| { type: 'history.reset'; generation: string; reason: 'rewind'; branchId: string };
|
|
166
192
|
|
|
167
193
|
export type Envelope = {
|
|
168
194
|
sessionId: string;
|
package/src/index.ts
CHANGED
package/src/rpc.ts
CHANGED
|
@@ -1,27 +1,67 @@
|
|
|
1
1
|
/** Portable JSON-RPC structures; implementations and provider SDKs stay outside protocol. */
|
|
2
2
|
import type { RuntimeKind, Pin } from './dto.js';
|
|
3
|
-
import type { AlpError, Envelope, SessionSnapshot, TreeStatus, UserQuestion } from './events.js';
|
|
4
|
-
import type { PauseState, PromptContent, RecallAnswer, SessionPreview, SessionSpec, SessionSummary } from './session.js';
|
|
3
|
+
import type { AlpError, Envelope, SessionSnapshot, TreeStatus, TurnAttention, UserQuestion } from './events.js';
|
|
4
|
+
import type { ConfigureOutcome, ConfigurePreview, ConversationBranch, ForkProvenance, PauseState, PromptContent, RecallAnswer, ResumeCommand, SessionPreview, SessionSpec, SessionSummary } from './session.js';
|
|
5
5
|
import type { EntryName, LanguageSetting, LibraryEntryRow, LibraryKind, LibraryScope, LibrarySource, TaskRow, TeamRow } from './dto.js';
|
|
6
|
+
import type { ScheduleJob, ScheduleStatus } from './schedule.js';
|
|
6
7
|
|
|
7
8
|
export type RpcId = string | number | null;
|
|
8
9
|
export type RpcErrorData = { code: number; message: string; data?: unknown };
|
|
9
10
|
export type RpcRequest = { jsonrpc: '2.0'; id?: RpcId; method: string; params?: Record<string, unknown> };
|
|
10
11
|
export type RpcResponse = { jsonrpc: '2.0'; id: RpcId } & ({ result: unknown; error?: never } | { error: RpcErrorData; result?: never });
|
|
11
12
|
export type EventNotification = { jsonrpc: '2.0'; method: 'event'; params: Envelope };
|
|
13
|
+
/** Why a terminal ended (§12.10). */
|
|
14
|
+
export type TerminalEndReason = 'exited' | 'closed' | 'idle' | 'lifetime' | 'detached' | 'revoked' | 'disabled' | 'shutdown';
|
|
15
|
+
/** A terminal as clients see it (capability `terminal`, §12.10): never a process id, shell path or host path. */
|
|
16
|
+
export type TerminalInfo = {
|
|
17
|
+
terminalId: string; workspaceId: string; title: string; cols: number; rows: number; state: 'running' | 'exited';
|
|
18
|
+
attached: boolean; createdAt: string; lastActivityAt: string; expiresAt: string;
|
|
19
|
+
exitCode?: number | null; signal?: number | null; endReason?: TerminalEndReason;
|
|
20
|
+
};
|
|
21
|
+
/** Effective terminal limits a daemon.hello advertises with capability `terminal`. */
|
|
22
|
+
export type TerminalLimits = {
|
|
23
|
+
maxPerPrincipal: number; maxPerDaemon: number; maxInputBytes: number; maxOutputFrameBytes: number;
|
|
24
|
+
replayBytes: number; idleMs: number; maxLifetimeMs: number; reconnectMs: number;
|
|
25
|
+
};
|
|
26
|
+
/** params of a terminal.event notification, sent only to the connection attached to the terminal. */
|
|
27
|
+
export type TerminalEvent =
|
|
28
|
+
| { terminalId: string; type: 'output'; seq: number; data: string }
|
|
29
|
+
| { terminalId: string; type: 'exit'; seq: number; exitCode: number | null; signal: number | null; reason: TerminalEndReason }
|
|
30
|
+
| { terminalId: string; type: 'detached'; reason: 'replaced' | 'slow_consumer' };
|
|
31
|
+
export type TerminalNotification = { jsonrpc: '2.0'; method: 'terminal.event'; params: TerminalEvent };
|
|
12
32
|
|
|
13
33
|
/** Names a daemon may list in its daemon.hello result (schema/methods-v1.json `capabilities`). */
|
|
14
|
-
export type
|
|
34
|
+
export type HistoryItem = Envelope | { cursor: number; oversized: { bytes: number } };
|
|
35
|
+
export type HistoryPage = { generation: string; items: HistoryItem[]; olderCursor?: string; hasOlder: boolean; tipCursor: number };
|
|
36
|
+
|
|
37
|
+
export type Capability = 'session.interrupt.turn' | 'session.fork' | 'session.rewind' | 'session.reload' | 'session.resumeCommand' | 'session.configure.live' | 'terminal' | 'attachments' | 'workspace.git' | 'workspace.worktree' | 'workspace.registry' | 'session.history' | 'attach.after' | 'prompt.receipts' | 'extension.methods' | 'team' | 'task' | 'library' | 'library.project.write' | 'settings.language' | 'session.list.page' | 'question.watch' | 'question.watch.turns' | 'session.attention' | 'workspace.files' | 'workspace.files.read' | 'prompt.images' | 'schedule';
|
|
15
38
|
|
|
16
39
|
/** `data.reason` values alpd sets on errors (docs/protocol/alp-json-rpc-v1.md §5.3). */
|
|
17
40
|
export type ErrorReason =
|
|
18
41
|
| 'hello_required' | 'protocol_version' | 'invalid_params' | 'invalid_id' | 'batch_unsupported' | 'frame_too_large'
|
|
42
|
+
| 'invalid_cursor' | 'stale_cursor' | 'history_gap'
|
|
43
|
+
| 'stale_revision' | 'request_conflict' | 'cwd_mismatch' | 'workspace_mismatch' | 'corrupt_registry' | 'identity_changed'
|
|
19
44
|
| 'response_too_large' | 'persistence_failed' | 'outcome_unknown' | 'key_conflict'
|
|
20
45
|
// Project, task and library methods (docs/protocol/alp-json-rpc-v1.md §5.3).
|
|
21
46
|
| 'invalid_path' | 'invalid_scope' | 'invalid_entry' | 'secret_marker' | 'not_alp_project' | 'unknown_task' | 'not_found'
|
|
22
47
|
| 'library_policy' | 'unsafe_path' | 'revision_conflict' | 'task_state' | 'in_use' | 'busy' | 'unreadable'
|
|
23
48
|
// workspace.files.search (§12.3); workspace.files.list and read (§12.4).
|
|
24
|
-
| 'not_directory' | 'blocked_path' | 'ignored' | 'linked' | 'binary' | 'too_large'
|
|
49
|
+
| 'not_directory' | 'blocked_path' | 'ignored' | 'linked' | 'binary' | 'too_large'
|
|
50
|
+
// workspace.git.status and workspace.git.diff (§12.5).
|
|
51
|
+
| 'not_repository' | 'unknown_ref' | 'invalid_compare' | 'stale_snapshot' | 'git_unavailable' | 'git_failed'
|
|
52
|
+
// Attachments (§12.6).
|
|
53
|
+
| 'invalid_name' | 'invalid_data' | 'attachment_too_large' | 'offset_mismatch' | 'chunk_conflict' | 'upload_incomplete'
|
|
54
|
+
| 'hash_mismatch' | 'upload_expired' | 'quota_exceeded' | 'attachment_referenced' | 'attachment_limit'
|
|
55
|
+
| 'attachment_unavailable' | 'attachment_unsupported'
|
|
56
|
+
// session.fork and session.rewind (§12.7).
|
|
57
|
+
| 'session_busy' | 'stale_generation' | 'turn_not_found' | 'turn_in_progress' | 'empty_prefix' | 'boundary_unmapped'
|
|
58
|
+
| 'runtime_unsupported' | 'native_refused' | 'branch_not_found' | 'attachment_forbidden'
|
|
59
|
+
// session.reload (§12.8).
|
|
60
|
+
| 'session_closed' | 'no_history'
|
|
61
|
+
// session.configure with model, thinking or team (§12.9).
|
|
62
|
+
| 'runtime_mismatch' | 'agent_mismatch' | 'permission_escalation' | 'team_not_found'
|
|
63
|
+
// Terminal (§12.10).
|
|
64
|
+
| 'terminal_disabled' | 'terminal_unsupported' | 'terminal_limit' | 'not_attached' | 'terminal_exited' | 'input_too_large';
|
|
25
65
|
|
|
26
66
|
type Empty = Record<string, never>;
|
|
27
67
|
|
|
@@ -30,8 +70,72 @@ type Empty = Record<string, never>;
|
|
|
30
70
|
* type-check the golden fixtures against this map, so the two cannot drift. Results may carry
|
|
31
71
|
* properties this map does not name: clients ignore them.
|
|
32
72
|
*/
|
|
73
|
+
export type RegistryProject = { id: string; root: string; canonical?: string; customName?: string | null; addedAt: string; removedAt?: string | null; revision?: number };
|
|
74
|
+
export type RegistryWorkspace = { branch?: string | null; managedWorktree?: boolean; id: string; projectId: string; directory: string; canonical?: string; title?: string | null; pinnedAt?: string | null; archivedAt?: string | null; labels: string[]; createdAt: string; revision?: number; idempotencyKey?: string };
|
|
75
|
+
/** workspace.git.status and workspace.git.diff (capability `workspace.git`, §12.5): paths are relative to the workspace, never host paths. */
|
|
76
|
+
export type GitChange = 'modified' | 'added' | 'deleted' | 'renamed' | 'copied' | 'typechange' | 'untracked' | 'conflicted';
|
|
77
|
+
export type GitCompare = { mode: 'uncommitted' | 'staged' | 'unstaged' | 'base'; baseRef?: string; ignoreWhitespace?: boolean };
|
|
78
|
+
export type GitStatusFile = { path: string; oldPath?: string; change: GitChange; staged: boolean; unstaged: boolean };
|
|
79
|
+
export type GitStatus = {
|
|
80
|
+
state: 'repository' | 'not_repository' | 'unsupported'; reason?: string;
|
|
81
|
+
branch: string | null; detached: boolean; head: string | null;
|
|
82
|
+
upstream: { ref: string; ahead: number; behind: number } | null;
|
|
83
|
+
baseRef: string | null; aheadBehind: { ahead: number; behind: number } | null;
|
|
84
|
+
hasRemote: boolean; remoteUrl: string | null; dirty: boolean;
|
|
85
|
+
files: GitStatusFile[]; hiddenCount: number; truncated: boolean; snapshot: string;
|
|
86
|
+
};
|
|
87
|
+
export type GitDiffLine = { type: 'add' | 'remove' | 'context'; content: string; noNewline?: true };
|
|
88
|
+
export type GitDiffHunk = { oldStart: number; oldCount: number; newStart: number; newCount: number; context?: string; lines: GitDiffLine[] };
|
|
89
|
+
export type GitDiffFile = { path: string; oldPath?: string; change: GitChange; kind: 'text' | 'binary' | 'too_large' | 'withheld' | 'symlink' | 'submodule'; additions: number; deletions: number; hunks: GitDiffHunk[] };
|
|
90
|
+
export type GitDiffPage = { compare: { mode: GitCompare['mode']; baseRef?: string; ignoreWhitespace: boolean }; snapshot: string; files: GitDiffFile[]; hiddenCount: number; truncated: boolean; nextCursor?: string };
|
|
91
|
+
/**
|
|
92
|
+
* Capability `attachments` (§12.6). Principal context is set only by a trusted gateway on its own
|
|
93
|
+
* connection; a local client leaves it out and acts as the owner.
|
|
94
|
+
*/
|
|
95
|
+
export type PrincipalContext = { principalId?: string; principalEpoch?: string; authorizedRoots?: string[] };
|
|
96
|
+
/** A committed attachment as clients see it: ids and display metadata, never a host path or URL. */
|
|
97
|
+
export type AttachmentInfo = {
|
|
98
|
+
attachmentId: string; sessionId: string; name: string; mimeType: string; detectedMimeType: string;
|
|
99
|
+
size: number; sha256: string; createdAt: string; committedAt: string; referenced: boolean;
|
|
100
|
+
};
|
|
101
|
+
/** Effective attachment limits a daemon.hello advertises with capability `attachments`. */
|
|
102
|
+
export type AttachmentLimits = {
|
|
103
|
+
maxObjectBytes: number; maxPerPrompt: number; maxPromptBytes: number; maxChunkBytes: number; maxActiveUploads: number;
|
|
104
|
+
maxPrincipalBytes: number; maxDaemonBytes: number; uploadTimeoutMs: number; unreferencedRetentionMs: number; releasedGraceMs: number;
|
|
105
|
+
};
|
|
106
|
+
/** Where a fork cuts the source conversation (§12.7): after a completed turn, or before a turn. */
|
|
107
|
+
export type ForkBoundary = { throughTurnId: string } | { beforeTurnId: string };
|
|
108
|
+
export type RegistryMutation = { requestId: string; ifRevision: number };
|
|
109
|
+
export type RegistryPage = { revision: number; total: number; nextCursor: string | null };
|
|
33
110
|
export type RpcMethodMap = {
|
|
34
|
-
'
|
|
111
|
+
'terminal.create': { params: PrincipalContext & { workspaceId: string; cols: number; rows: number; requestId: string }; result: { terminal: TerminalInfo } };
|
|
112
|
+
'terminal.list': { params: PrincipalContext & { workspaceId?: string }; result: { terminals: TerminalInfo[] } };
|
|
113
|
+
'terminal.attach': { params: PrincipalContext & { terminalId: string; after?: number }; result: { terminal: TerminalInfo; nextSeq: number; gap?: { fromSeq: number; toSeq: number } } };
|
|
114
|
+
'terminal.input': { params: PrincipalContext & { terminalId: string; data: string }; result: { bytes: number } };
|
|
115
|
+
'terminal.resize': { params: PrincipalContext & { terminalId: string; cols: number; rows: number }; result: { cols: number; rows: number } };
|
|
116
|
+
'terminal.close': { params: PrincipalContext & { terminalId: string }; result: { closed: true } };
|
|
117
|
+
'terminal.revoke': { params: { principalId: string }; result: { closed: number } };
|
|
118
|
+
'attachment.begin': { params: PrincipalContext & { sessionId: string; name: string; size: number; mimeType: string; sha256?: string; requestId: string }; result: { uploadId: string; state: 'uploading' | 'committed'; received: number; size: number; maxChunkBytes: number; expiresAt?: string; attachmentId?: string } };
|
|
119
|
+
'attachment.chunk': { params: PrincipalContext & { uploadId: string; offset: number; data: string }; result: { received: number; size: number } };
|
|
120
|
+
'attachment.commit': { params: PrincipalContext & { uploadId: string; sha256: string }; result: { attachment: AttachmentInfo } };
|
|
121
|
+
'attachment.abort': { params: PrincipalContext & { uploadId: string }; result: { aborted: true } };
|
|
122
|
+
'attachment.get': { params: PrincipalContext & { attachmentId: string }; result: { attachment: AttachmentInfo } };
|
|
123
|
+
'attachment.delete': { params: PrincipalContext & { attachmentId: string }; result: { deleted: true } };
|
|
124
|
+
'workspace.git.status': { params: { workspaceId: string }; result: GitStatus };
|
|
125
|
+
'workspace.git.diff': { params: { workspaceId: string; compare: GitCompare; path?: string; cursor?: string }; result: GitDiffPage };
|
|
126
|
+
'workspace.worktree.options': { params: { workspaceId: string }; result: { mappingIds: string[] } };
|
|
127
|
+
'workspace.worktree.create': { params: RegistryMutation & { workspaceId: string; mappingId: string }; result: { workspace: RegistryWorkspace; revision: number } };
|
|
128
|
+
'workspace.worktree.remove': { params: RegistryMutation & { workspaceId: string }; result: { workspace: RegistryWorkspace; revision: number; filesRemoved: true; branchRetained: true } };
|
|
129
|
+
'registry.worktree': { params: { principalId?: string; principalEpoch?: string; mappingIds?: string[]; authorizedRoots?: string[] }; result: { mappings: Array<{id:string;source:string}>; workspaces: unknown[] } };
|
|
130
|
+
'project.list': { params: { cursor?: string; limit?: number; projectRoots?: string[] }; result: RegistryPage & { projects: RegistryProject[] } };
|
|
131
|
+
'workspace.list': { params: { cursor?: string; limit?: number; projectRoots?: string[] }; result: RegistryPage & { workspaces: RegistryWorkspace[] } };
|
|
132
|
+
'workspace.get': { params: { workspaceId: string }; result: { workspace: RegistryWorkspace; revision: number } };
|
|
133
|
+
'project.register': { params: RegistryMutation & { directory: string }; result: { project: RegistryProject; revision: number } };
|
|
134
|
+
'project.remove': { params: RegistryMutation & { projectId: string }; result: { project: RegistryProject; revision: number } };
|
|
135
|
+
'workspace.update': { params: RegistryMutation & { workspaceId: string; title?: string | null; pinned?: boolean; archived?: boolean }; result: { workspace: RegistryWorkspace; revision: number } };
|
|
136
|
+
'registry.owner': { params: { operation?: string; args?: Record<string, unknown>; requestId?: string; ifRevision?: number }; result: { registry: Record<string, unknown> } };
|
|
137
|
+
|
|
138
|
+
'daemon.hello': { params: { protocolVersion: 1; client?: { name: string; version: string }; capabilities?: string[] }; result: { protocolVersion: 1; daemonVersion?: string; pid?: number; /** The daemon host's machine name (os.hostname()), a label for clients. */ hostname?: string; promptImageLimits?: { maxCount: number; maxBytes: number; maxTotalBytes: number }; attachmentLimits?: AttachmentLimits; terminalLimits?: TerminalLimits; capabilities?: string[] } };
|
|
35
139
|
'daemon.status': {
|
|
36
140
|
params: Empty;
|
|
37
141
|
result: {
|
|
@@ -50,8 +154,10 @@ export type RpcMethodMap = {
|
|
|
50
154
|
'assignment.recall': { params: { assignmentId?: string; taskId?: string; projectRoot?: string; question: string }; result: RecallAnswer };
|
|
51
155
|
'board.list': { params: { projectRoot: string }; result: { pins: Pin[] } };
|
|
52
156
|
'question.list': { params: { projectRoot?: string }; result: { questions: UserQuestion[] } };
|
|
53
|
-
/** Capability `question.watch`: the waiting questions now; then this connection is told of every tree's `question` and `question.resolved` (of `projectRoot` when given). `turns` (capability `question.watch.turns`): also every root's `turn.ended`. */
|
|
54
|
-
'question.watch': { params: { projectRoot?: string; turns?: boolean }; result: { questions: UserQuestion[] } };
|
|
157
|
+
/** Capability `question.watch`: the waiting questions now; then this connection is told of every tree's `question` and `question.resolved` (of `projectRoot` when given). `turns` (capability `question.watch.turns`): also every root's `turn.ended`. `attention` (capability `session.attention`): also each root's attention, now and as it changes. */
|
|
158
|
+
'question.watch': { params: { projectRoot?: string; turns?: boolean; attention?: boolean }; result: { questions: UserQuestion[]; attention?: Array<{ sessionId: string; attention: TurnAttention }> } };
|
|
159
|
+
/** Capability `session.attention`: acknowledges a root's attention for every client; with `at`, only that one (a later turn's stays). */
|
|
160
|
+
'session.attention.clear': { params: { sessionId: string; at?: string }; result: { cleared: boolean } };
|
|
55
161
|
/** Capability `workspace.files`: files under `projectRoot` whose relative path matches `query`, best first; read-only, links not followed. */
|
|
56
162
|
'workspace.files.search': { params: { projectRoot: string; query?: string; limit?: number }; result: { files: Array<{ path: string }>; truncated?: boolean } };
|
|
57
163
|
/** Capability `workspace.files.read`: the entries of a directory under `projectRoot` (its root when `path` is left out); links, blocked and (unless `includeIgnored`) .gitignore'd entries left out. */
|
|
@@ -60,21 +166,69 @@ export type RpcMethodMap = {
|
|
|
60
166
|
'workspace.files.read': { params: { projectRoot: string; path: string; includeIgnored?: boolean }; result: { path: string; content: string; size: number; modifiedAt?: string } };
|
|
61
167
|
'question.answer': { params: { questionId: string; text?: string; dismiss?: boolean; reason?: string }; result: { questionId: string } };
|
|
62
168
|
'session.create': {
|
|
63
|
-
params: { sessionId?: string;
|
|
169
|
+
params: { sessionId?: string; history?: 'skip' | 'replay'; delegation?: boolean; resume?: boolean } & ({ workspaceId: string; spec: Omit<SessionSpec, 'cwd'> & { cwd?: string } } | { workspaceId?: undefined; spec: SessionSpec });
|
|
64
170
|
result: { session: SessionSnapshot; attached: boolean; resumed?: true; durable?: boolean };
|
|
65
171
|
};
|
|
66
172
|
'session.preview': { params: { spec: SessionSpec }; result: { preview: SessionPreview } };
|
|
67
|
-
'session.
|
|
173
|
+
'session.history': { params: { sessionId: string; before?: string; limit?: number; maxBytes?: number }; result: HistoryPage };
|
|
174
|
+
'session.attach': { params: { sessionId: string; replay?: boolean; after?: number; generation?: string }; result: { session: SessionSummary; cursor: number; reset?: true } };
|
|
68
175
|
'session.release': { params: { sessionId: string }; result: { closed: boolean } };
|
|
69
176
|
'session.prompt': {
|
|
70
|
-
params: { sessionId: string; clientMessageId: string; delivery?: 'auto' | 'steer'; content: PromptContent };
|
|
177
|
+
params: PrincipalContext & { sessionId: string; clientMessageId: string; delivery?: 'auto' | 'steer'; content: PromptContent };
|
|
71
178
|
/** `{}` from a daemon without prompt.receipts. */
|
|
72
179
|
result: { outcome?: 'accepted' | 'failed'; error?: AlpError; duplicate?: boolean; durable?: boolean };
|
|
73
180
|
};
|
|
74
|
-
'session.interrupt': { params: { sessionId: string }; result:
|
|
75
|
-
|
|
181
|
+
'session.interrupt': { params: { sessionId: string; /** With session.interrupt.turn, cancel only this turn. */ turnId?: string }; result: { /** Conditional request only: false means the named turn is no longer active. */ interrupted?: boolean } };
|
|
182
|
+
/**
|
|
183
|
+
* Changes a session's permissions mode while idle; with capability `session.configure.live`, also a live
|
|
184
|
+
* root's model, effort or team (§12.9): those need requestId and ifGeneration, apply at the idle barrier
|
|
185
|
+
* through a restart of its native process on the same conversation, and answer `configured`. Answered
|
|
186
|
+
* once per (sessionId, requestId): a repeat answers the first result with `duplicate: true`.
|
|
187
|
+
*/
|
|
188
|
+
'session.configure': {
|
|
189
|
+
params: { sessionId: string; mode?: string; model?: string; thinking?: string; team?: string; requestId?: string; ifGeneration?: string };
|
|
190
|
+
result: { session: SessionSnapshot | SessionSummary; configured?: ConfigureOutcome; duplicate?: true };
|
|
191
|
+
};
|
|
192
|
+
/** What session.configure would run a live root with, and whether it would take it now (capability `session.configure.live`, §12.9). */
|
|
193
|
+
'session.configure.preview': {
|
|
194
|
+
params: { sessionId: string; mode?: string; model?: string; thinking?: string; team?: string };
|
|
195
|
+
result: { preview: ConfigurePreview };
|
|
196
|
+
};
|
|
76
197
|
'session.close': { params: { sessionId: string }; result: Empty };
|
|
77
198
|
'session.rename': { params: { sessionId: string; title: string }; result: { session: SessionSummary } };
|
|
199
|
+
/**
|
|
200
|
+
* A new root whose conversation is the source's up to a completed turn (capability `session.fork`, §12.7).
|
|
201
|
+
* Answered once per (sessionId, requestId): a repeat answers the first result with `duplicate: true`.
|
|
202
|
+
*/
|
|
203
|
+
'session.fork': {
|
|
204
|
+
params: PrincipalContext & { sessionId: string; requestId: string; ifGeneration: string; boundary?: ForkBoundary; branchId?: string; workspaceId?: string; newSessionId?: string; title?: string };
|
|
205
|
+
result: { session: SessionSummary; forkedFrom: ForkProvenance; duplicate?: true };
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* The root's conversation from before a turn on, under the same session id; the later turns stay as a
|
|
209
|
+
* recoverable branch and no file is changed (capability `session.rewind`, §12.7).
|
|
210
|
+
*/
|
|
211
|
+
'session.rewind': {
|
|
212
|
+
params: { sessionId: string; requestId: string; ifGeneration: string; beforeTurnId: string };
|
|
213
|
+
result: { session: SessionSummary; generation: string; branch: ConversationBranch; duplicate?: true };
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Restarts a live root's native process on its own conversation and configuration at the idle
|
|
217
|
+
* barrier, keeping its session id, history and receipts (capability `session.reload`, §12.8).
|
|
218
|
+
* Answered once per (sessionId, requestId): a repeat answers the first result with `duplicate: true`.
|
|
219
|
+
*/
|
|
220
|
+
'session.reload': {
|
|
221
|
+
params: { sessionId: string; requestId: string; ifGeneration: string };
|
|
222
|
+
result: { session: SessionSummary; duplicate?: true };
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* The ALP CLI command that continues a session in a terminal, built by alpd (capability
|
|
226
|
+
* `session.resumeCommand`, §12.8): argv and its POSIX and Windows command lines, for the clipboard only.
|
|
227
|
+
*/
|
|
228
|
+
'session.resumeCommand': {
|
|
229
|
+
params: { sessionId: string };
|
|
230
|
+
result: ResumeCommand;
|
|
231
|
+
};
|
|
78
232
|
'session.archive': { params: { sessionId: string; archived?: boolean }; result: { session: SessionSummary } };
|
|
79
233
|
'session.get': { params: { sessionId: string }; result: { session: SessionSummary } };
|
|
80
234
|
/** `limit`, `cursor`, `query` and `projectRoots` with capability `session.list.page`: a page, and `nextCursor` while more follow. */
|
|
@@ -106,6 +260,11 @@ export type RpcMethodMap = {
|
|
|
106
260
|
'library.models': { params: Empty; result: { models: Array<{ id: string; label: string; efforts: string[]; effort?: string }> } };
|
|
107
261
|
'settings.language.get': { params: Empty; result: LanguageSetting };
|
|
108
262
|
'settings.language.set': { params: { language: string | null }; result: LanguageSetting };
|
|
263
|
+
/** Capability `schedule`: a one-shot continuation of a root session, due at `dueAt` (UTC, ISO 8601). */
|
|
264
|
+
'schedule.create': { params: { projectRoot: string; sessionId: string; dueAt: string; prompt: string }; result: { job: ScheduleJob } };
|
|
265
|
+
'schedule.list': { params: { projectRoot: string; status?: ScheduleStatus }; result: { jobs: ScheduleJob[] } };
|
|
266
|
+
'schedule.get': { params: { id: string; projectRoot: string }; result: { job: ScheduleJob } };
|
|
267
|
+
'schedule.cancel': { params: { id: string; projectRoot: string }; result: { job: ScheduleJob } };
|
|
109
268
|
};
|
|
110
269
|
|
|
111
270
|
export type RpcMethod = keyof RpcMethodMap;
|
package/src/schedule.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A scheduled continuation: a one-shot job that opens a new turn on a root session at a
|
|
3
|
+
* given time, without a user message. The protocol owns the shape so every
|
|
4
|
+
* client (the CLI, a viewer, an agent tool) reads the same state.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** Where a job stands: waiting for its time, delivered, failed, or cancelled before it fired. */
|
|
8
|
+
export type ScheduleStatus = 'pending' | 'delivered' | 'failed' | 'cancelled';
|
|
9
|
+
|
|
10
|
+
/** Why a job failed or still waits; failed may mean execution is unknown. */
|
|
11
|
+
export type ScheduleError = { message: string; code?: string };
|
|
12
|
+
|
|
13
|
+
/** One job as `schedule.list` and `schedule.get` answer it. */
|
|
14
|
+
export type ScheduleJob = {
|
|
15
|
+
/** The job's own id, `sch_` and sixteen hex digits. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** The project whose root session the job continues. */
|
|
18
|
+
projectRoot: string;
|
|
19
|
+
/** The root session the job opens a turn on. */
|
|
20
|
+
sessionId: string;
|
|
21
|
+
/** The continuation the job delivers. */
|
|
22
|
+
prompt: string;
|
|
23
|
+
/** When the job is due, in UTC (ISO 8601). */
|
|
24
|
+
dueAt: string;
|
|
25
|
+
status: ScheduleStatus;
|
|
26
|
+
/**
|
|
27
|
+
* How many times alpd tried to deliver the job. A job delivered once may have been
|
|
28
|
+
* tried twice: a retry after a crash reads the receipt of the first delivery.
|
|
29
|
+
*/
|
|
30
|
+
attempts: number;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
/** When the job was delivered, failed or cancelled. */
|
|
34
|
+
deliveredAt?: string;
|
|
35
|
+
failedAt?: string;
|
|
36
|
+
cancelledAt?: string;
|
|
37
|
+
/** When the first delivery was tried. */
|
|
38
|
+
attemptedAt?: string;
|
|
39
|
+
/** How late the delivery was, in milliseconds; 0 when it was on time. */
|
|
40
|
+
lateByMs?: number;
|
|
41
|
+
/** The last error of the job: why it failed, or why it still waits. */
|
|
42
|
+
lastError?: ScheduleError;
|
|
43
|
+
};
|
package/src/session.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RuntimeKind } from './dto.js';
|
|
2
|
-
import type { SessionSnapshot } from './events.js';
|
|
2
|
+
import type { SessionSnapshot, TurnAttention } from './events.js';
|
|
3
3
|
/** MCP servers a client adds to the agent's own; the agent's names win no collisions. */
|
|
4
4
|
export type HostMcpServer =
|
|
5
5
|
| { type: 'stdio'; command: string; args?: string[]; env?: Record<string, string> }
|
|
@@ -22,7 +22,11 @@ export type SessionSpec = {
|
|
|
22
22
|
persist?: boolean;
|
|
23
23
|
/** Keep the native thread on disk though the session cannot be resumed: an assignment, which alp_recall may question later. */
|
|
24
24
|
keepThread?: boolean;
|
|
25
|
-
restore?: {
|
|
25
|
+
restore?: {
|
|
26
|
+
agent: string; threadId: string; runtime?: string; model?: string; workflow?: { mode: string; maxPeers: number; supervisor?: boolean };
|
|
27
|
+
/** Claude: `threadId` does not exist yet and is made from this conversation on its first prompt (a fork or rewind, §12.7). */
|
|
28
|
+
base?: NativeBase;
|
|
29
|
+
};
|
|
26
30
|
/** Where the native harness works, when not the project root: an assignment's git worktree. ALP files are still read from cwd. */
|
|
27
31
|
workdir?: string;
|
|
28
32
|
/** Explicit shared-assignment write scope; workdir points here while ALP uses cwd for its project. */
|
|
@@ -41,8 +45,17 @@ export type OpenOptions = {
|
|
|
41
45
|
|
|
42
46
|
/** An image inline in a prompt (capability `prompt.images`): base64 data, never a path or URL; limits in PROMPT_IMAGES. */
|
|
43
47
|
export type PromptImage = { type: 'image'; mimeType: 'image/png' | 'image/jpeg' | 'image/gif' | 'image/webp'; data: string; name?: string };
|
|
44
|
-
/**
|
|
45
|
-
export type
|
|
48
|
+
/** A stored attachment in a prompt (capability `attachments`, §12.6): its opaque id only, never a path or URL. */
|
|
49
|
+
export type PromptAttachment = { type: 'attachment'; attachmentId: string };
|
|
50
|
+
/**
|
|
51
|
+
* What alpd hands a runtime for an attachment part once it validated and pinned it (§12.6, the
|
|
52
|
+
* runtime seam of issue #121 branch J): ids, display metadata and digest, never a host path. The
|
|
53
|
+
* runtime reads the bytes through the AttachmentReader alpd binds, and only for a session whose
|
|
54
|
+
* runtime declared it takes attachments.
|
|
55
|
+
*/
|
|
56
|
+
export type RuntimeAttachment = { type: 'attachment'; attachmentId: string; name: string; mimeType: string; detectedMimeType: string; size: number; sha256: string };
|
|
57
|
+
/** Text parts carry `text`; image parts (`type: 'image'`) carry PromptImage's fields; attachment parts (`type: 'attachment'`) `attachmentId`. */
|
|
58
|
+
export type PromptContent = Array<{ type: string; text?: string; mimeType?: string; data?: string; name?: string; attachmentId?: string }>;
|
|
46
59
|
|
|
47
60
|
export type PromptInput = {
|
|
48
61
|
clientMessageId: string;
|
|
@@ -91,7 +104,110 @@ export type SessionSummary = SessionSnapshot & {
|
|
|
91
104
|
status: SessionStatus;
|
|
92
105
|
title?: string;
|
|
93
106
|
archived?: boolean;
|
|
107
|
+
/** A root's turn that ended unseen, until a client acknowledges it (capability `session.attention`). */
|
|
108
|
+
attention?: TurnAttention;
|
|
94
109
|
lastError?: SessionError;
|
|
95
110
|
createdAt?: string;
|
|
96
111
|
updatedAt?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Whether alpd can fork or rewind this session's conversation now, and why not (capabilities
|
|
114
|
+
* `session.fork` and `session.rewind`, §12.7). A single turn may still be refused: one in
|
|
115
|
+
* progress, the first one, or one alpd cannot map to the runtime's own history.
|
|
116
|
+
*/
|
|
117
|
+
conversation?: {
|
|
118
|
+
fork: boolean; rewind: boolean;
|
|
119
|
+
/** Whether session.reload can restart this root's native process (capability `session.reload`, §12.8); the root must also be open and idle. */
|
|
120
|
+
reload?: boolean;
|
|
121
|
+
/** Whether session.configure can change this root's model, effort or team (capability `session.configure.live`, §12.9); the root must also be open and idle. */
|
|
122
|
+
configure?: boolean;
|
|
123
|
+
reason?: 'runtime_unsupported' | 'not_persistent' | 'not_root';
|
|
124
|
+
};
|
|
125
|
+
/** The session this root was forked from (§12.7). */
|
|
126
|
+
forkedFrom?: ForkProvenance;
|
|
127
|
+
/** Conversation branches rewinds set aside, oldest first; each stays recoverable with session.fork { branchId } (§12.7). */
|
|
128
|
+
branches?: ConversationBranch[];
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A Claude conversation ALP has bound a session to but the runtime has not written yet (§12.7): the
|
|
133
|
+
* first prompt resumes `threadId` as a new session, keeping its history through the entry `at` (all of
|
|
134
|
+
* it when absent). Internal to alpd and its runtime; never accepted from a remote client.
|
|
135
|
+
*/
|
|
136
|
+
export type NativeBase = { threadId: string; at?: string };
|
|
137
|
+
|
|
138
|
+
/** Where a fork came from (§12.7): the source root, and the turn its conversation was cut at. */
|
|
139
|
+
export type ForkProvenance = {
|
|
140
|
+
sessionId: string;
|
|
141
|
+
/** The cut: through this turn, or before it; absent when the whole conversation was taken. */
|
|
142
|
+
turnId?: string;
|
|
143
|
+
boundary: 'through' | 'before' | 'whole';
|
|
144
|
+
/** Set when the fork restored a branch a rewind set aside. */
|
|
145
|
+
branchId?: string;
|
|
146
|
+
at: string;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** The later turns of a conversation a rewind set aside (§12.7): kept, never deleted, recoverable as a new session. */
|
|
150
|
+
export type ConversationBranch = {
|
|
151
|
+
branchId: string;
|
|
152
|
+
/** The turn the rewind went back to before; it and every later turn are in the branch. */
|
|
153
|
+
beforeTurnId: string;
|
|
154
|
+
/** How many turns the branch holds past the kept conversation. */
|
|
155
|
+
turns: number;
|
|
156
|
+
at: string;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The ALP CLI command that continues a session in a terminal (session.resumeCommand, §12.8): `argv`
|
|
161
|
+
* and the same command quoted for a POSIX shell and for Windows (cmd.exe). `display` is the one for
|
|
162
|
+
* the daemon host's own platform. Never a native runtime's command, credential, env or path.
|
|
163
|
+
*/
|
|
164
|
+
export type ResumeCommand = { argv: string[]; posix: string; windows: string; display: string; platform: 'posix' | 'windows' };
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* The configuration a root runs with, or would after a change, as alpd resolved it (§12.9): its team,
|
|
168
|
+
* agent, runtime, model, effort and mode, and what they bring (delegation targets, ALP tools, MCP
|
|
169
|
+
* servers by name and kind, hook count, an instructions digest). Never a path, command or credential.
|
|
170
|
+
*/
|
|
171
|
+
export type EffectiveConfig = {
|
|
172
|
+
team: string;
|
|
173
|
+
teamLabel?: string;
|
|
174
|
+
agent: string;
|
|
175
|
+
runtime: RuntimeKind;
|
|
176
|
+
model: string;
|
|
177
|
+
thinking: string;
|
|
178
|
+
mode: string;
|
|
179
|
+
/** The permission profile capping the agent's mode, when one does. */
|
|
180
|
+
permissionProfile?: string;
|
|
181
|
+
supervisor: boolean;
|
|
182
|
+
maxPeers?: number;
|
|
183
|
+
delegates: string[];
|
|
184
|
+
tools: string[];
|
|
185
|
+
mcpServers: Array<{ name: string; kind: 'stdio' | 'http' }>;
|
|
186
|
+
hooks: number;
|
|
187
|
+
instructionsSha?: string;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export type ConfigureChange = 'model' | 'thinking' | 'team' | 'mode';
|
|
191
|
+
export type ConfigureRefusal = 'session_busy' | 'session_closed' | 'not_root' | 'runtime_unsupported' | 'runtime_mismatch' | 'agent_mismatch' | 'permission_escalation' | 'team_not_found' | 'invalid_params' | 'not_persistent';
|
|
192
|
+
|
|
193
|
+
/** session.configure.preview (§12.9): the current and proposed configuration, and whether alpd would apply it now. */
|
|
194
|
+
export type ConfigurePreview = {
|
|
195
|
+
current: EffectiveConfig;
|
|
196
|
+
proposed: EffectiveConfig;
|
|
197
|
+
changes: ConfigureChange[];
|
|
198
|
+
/** Applying it restarts the root's native process on its own conversation. */
|
|
199
|
+
restart: boolean;
|
|
200
|
+
/** session.configure would take it now; a tree that gets busy meanwhile still refuses it. */
|
|
201
|
+
allowed: boolean;
|
|
202
|
+
reason?: ConfigureRefusal;
|
|
203
|
+
message?: string;
|
|
204
|
+
teams: Array<{ id: string; label: string; description?: string }>;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/** What a session.configure with model, thinking or team did (§12.9): what was asked, and what the root runs with now. */
|
|
208
|
+
export type ConfigureOutcome = {
|
|
209
|
+
status: 'applied' | 'unchanged';
|
|
210
|
+
requested: { model?: string; thinking?: string; team?: string; mode?: string };
|
|
211
|
+
effective: EffectiveConfig;
|
|
212
|
+
restarted: boolean;
|
|
97
213
|
};
|