@agent-compose/sdk 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +213 -189
  2. package/dist/agent/agent-context.d.ts +3 -3
  3. package/dist/agent/agent-loop.d.ts +4 -5
  4. package/dist/agent/perf-sampler.d.ts +27 -2
  5. package/dist/agent/run-agent.d.ts +1 -1
  6. package/dist/client.d.ts +105 -52
  7. package/dist/directives.d.ts +3 -3
  8. package/dist/display.d.ts +7 -0
  9. package/dist/errors.d.ts +1 -1
  10. package/dist/generated/agentc-commands.d.ts +34 -0
  11. package/dist/index.d.ts +12 -12
  12. package/dist/index.js +716 -203
  13. package/dist/request-context/request-context.d.ts +1 -1
  14. package/dist/runtimes/_cli-agent.d.ts +182 -68
  15. package/dist/runtimes/claude-code.d.ts +60 -1
  16. package/dist/runtimes/claude.d.ts +1 -1
  17. package/dist/runtimes/codex.d.ts +94 -6
  18. package/dist/runtimes/codex.mid-turn-hook.test.d.ts +10 -0
  19. package/dist/runtimes/openai-desktop.js +686 -199
  20. package/dist/runtimes/opencode.d.ts +48 -11
  21. package/dist/runtimes/opencode.test.d.ts +14 -0
  22. package/dist/sandbox/baked-clis.d.ts +75 -0
  23. package/dist/sandbox/exec-stream.d.ts +1 -2
  24. package/dist/sandbox/network-policy.d.ts +23 -5
  25. package/dist/sandbox.d.ts +4 -2
  26. package/dist/step-invocation/protocol.d.ts +3 -4
  27. package/dist/step-invocation/server.d.ts +2 -2
  28. package/dist/step-invocation/types.d.ts +1 -1
  29. package/dist/types/api-conversations.d.ts +442 -29
  30. package/dist/types/api-factory.d.ts +78 -8
  31. package/dist/types/api-projects.d.ts +480 -0
  32. package/dist/types/api-runs.d.ts +8 -0
  33. package/dist/types/api-scopes.d.ts +32 -3
  34. package/dist/types/conversation-stream.d.ts +5 -0
  35. package/dist/types/execution-context.d.ts +1 -1
  36. package/dist/types/protocol.d.ts +65 -2
  37. package/dist/types/runtime.d.ts +9 -2
  38. package/dist/types/workflow-metadata.d.ts +2 -4
  39. package/dist/types/workflow-plan.d.ts +1 -3
  40. package/dist/utils/bundler.d.ts +23 -0
  41. package/dist/workflow-steps/observability.d.ts +2 -3
  42. package/dist/workflow-steps/runner.d.ts +5 -8
  43. package/dist/workflow-steps/types.d.ts +8 -10
  44. package/dist/workflow-steps/workflow.d.ts +2 -1
  45. package/dist/workflows/engine.d.ts +3 -5
  46. package/dist/workflows/invoke-child.d.ts +2 -2
  47. package/package.json +2 -2
  48. package/src/agent/agent-context.ts +168 -125
  49. package/src/agent/agent-loop.ts +5 -4
  50. package/src/agent/perf-sampler.ts +54 -3
  51. package/src/agent/run-agent.ts +1 -1
  52. package/src/client.ts +191 -71
  53. package/src/directives.ts +3 -3
  54. package/src/display.ts +12 -0
  55. package/src/errors.ts +1 -0
  56. package/src/generated/agentc-commands.ts +571 -0
  57. package/src/index.ts +54 -21
  58. package/src/pause/pause-core.ts +2 -1
  59. package/src/request-context/request-context.ts +1 -1
  60. package/src/runtimes/_cli-agent.ts +306 -122
  61. package/src/runtimes/claude-code.ts +179 -9
  62. package/src/runtimes/claude.ts +1 -1
  63. package/src/runtimes/codex.ts +188 -19
  64. package/src/runtimes/opencode.ts +195 -26
  65. package/src/sandbox/baked-clis.ts +86 -0
  66. package/src/sandbox/exec-stream.ts +1 -2
  67. package/src/sandbox/network-policy.ts +51 -7
  68. package/src/sandbox/providers/e2b.ts +3 -3
  69. package/src/sandbox/providers/vercel.ts +6 -6
  70. package/src/sandbox.ts +8 -2
  71. package/src/step-invocation/invoker.ts +2 -6
  72. package/src/step-invocation/protocol.ts +3 -4
  73. package/src/step-invocation/server.ts +2 -2
  74. package/src/types/api-conversations.ts +366 -23
  75. package/src/types/api-factory.ts +74 -8
  76. package/src/types/api-projects.ts +443 -0
  77. package/src/types/api-runs.ts +5 -0
  78. package/src/types/api-scopes.ts +32 -3
  79. package/src/types/conversation-stream.ts +5 -0
  80. package/src/types/execution-context.ts +1 -1
  81. package/src/types/protocol.ts +67 -1
  82. package/src/types/runtime.ts +8 -2
  83. package/src/types/sandbox-environment.ts +1 -2
  84. package/src/types/workflow-metadata.ts +2 -4
  85. package/src/types/workflow-plan.ts +1 -3
  86. package/src/utils/bundler.ts +88 -19
  87. package/src/workflow-steps/observability.ts +2 -3
  88. package/src/workflow-steps/runner.ts +5 -8
  89. package/src/workflow-steps/types.ts +8 -10
  90. package/src/workflow-steps/workflow.ts +2 -1
  91. package/src/workflows/engine.ts +3 -5
  92. package/src/workflows/invoke-child.ts +2 -2
  93. package/dist/generated/verb-synopsis.d.ts +0 -34
  94. package/dist/pause/__tests__/errors.test.d.ts +0 -1
  95. package/dist/pause/__tests__/wrappers.test.d.ts +0 -1
  96. package/dist/step-invocation/__tests__/protocol.test.d.ts +0 -1
  97. package/src/generated/verb-synopsis.ts +0 -544
@@ -47,8 +47,32 @@ export const PERF_PROBE_WINDOW_SECONDS = 2;
47
47
  /** The standalone probe's output line leads with this prefix. */
48
48
  export const PERF_PROBE_LINE_PREFIX = "perf ";
49
49
 
50
- /** Longest token the parsers accept — anything bigger is garbage. */
51
- export const PERF_TOKEN_MAX_CHARS = 200;
50
+ /** Longest token the parsers accept — anything bigger is garbage. The
51
+ * `top` field (three process names of up to 20 chars with their RSS) is
52
+ * what moved this up from 200. */
53
+ export const PERF_TOKEN_MAX_CHARS = 320;
54
+
55
+ /** THE TOP PROCESSES BY RESIDENT MEMORY ("why was it at 99%?", the
56
+ * 2026-10-03 flight incident: a 4 vCPU / 8 GB machine sat at 99% memory
57
+ * for twelve minutes and nothing recorded what held it): at most this many
58
+ * entries ride the token, name and resident MB each. A machine fact, read
59
+ * by the same /proc burst; never a judgment. */
60
+ export const PERF_TOP_PROCESSES = 3;
61
+ /** A process name as the token carries it: `comm`, sanitized on the guest
62
+ * to this alphabet and length, so it can ride a comma-separated token. */
63
+ export const PERF_TOP_NAME_MAX = 20;
64
+ const TOP_NAME_RE = /^[A-Za-z0-9._-]{1,20}$/;
65
+ /** A process resident set the clamp believes, in MB (a 64 GB machine is
66
+ * past every sandbox we run). */
67
+ const TOP_RSS_MAX_MB = 65_536;
68
+
69
+ /** One process by resident memory. */
70
+ export interface GuestTopProcess {
71
+ /** Its `comm` (the executable's short name), sanitized. */
72
+ name: string;
73
+ /** Resident set in MB, whole. */
74
+ rssMb: number;
75
+ }
52
76
 
53
77
  /** One clamped guest perf sample. Null fields = unreadable/absent on the
54
78
  * guest — never zero-filled (a zero is a claim; null is honesty). */
@@ -67,6 +91,9 @@ export interface GuestPerfSample {
67
91
  ramMb: number | null;
68
92
  /** Guest clock at sample time, epoch seconds. Dedupe only. */
69
93
  sampledAtS: number | null;
94
+ /** The processes holding the most memory, biggest first (`top`); null
95
+ * when the guest did not report them (an older image, no `ps`). */
96
+ topProcesses: GuestTopProcess[] | null;
70
97
  }
71
98
 
72
99
  export interface PerfSamplerPaths {
@@ -113,7 +140,13 @@ function perfReadFunctionFragment(procRoot: string, diskPath: string): string {
113
140
  + `if [ "\${ac_pmt:-0}" -gt 0 ] && [ -n "\${ac_pma:-}" ]; then ac_pmem=$(( 100 * (ac_pmt - ac_pma) / ac_pmt )); fi; `
114
141
  + `[ "$ac_pmem" -gt 100 ] && ac_pmem=100; [ "$ac_pmem" -lt -1 ] && ac_pmem=-1; `
115
142
  + `ac_pdsk=$(df -kP ${disk} 2>/dev/null | awk 'NR==2 && $2>0 {print int(100*$3/$2)}'); `
116
- + `ac_tok="v=1,cpu=$ac_pcpu,l1=\${ac_pl1:--1},mem=$ac_pmem,dsk=\${ac_pdsk:--1},vc=\${ac_pvc:-0},ram=$(( \${ac_pmt:-0} / 1024 )),ts=$(date +%s)"; `
143
+ // The top processes by resident memory (the "why 99%?" fact): three
144
+ // `comm` names with their RSS in MB, each name sanitized to the token's
145
+ // alphabet, `;`-joined so the field stays one comma-free word. A guest
146
+ // whose `ps` lacks `--sort` (not procps) prints nothing and the field is
147
+ // absent — the parser reads that as "not reported", never as zero.
148
+ + `ac_ptop=$(ps -eo rss=,comm= --sort=-rss 2>/dev/null | head -${PERF_TOP_PROCESSES} | awk '{ n=$2; gsub(/[^A-Za-z0-9._-]/, "_", n); if (length(n) > ${PERF_TOP_NAME_MAX}) n=substr(n, 1, ${PERF_TOP_NAME_MAX}); if (n == "") n="_"; printf "%s%s:%d", (NR > 1 ? ";" : ""), n, int($1 / 1024) }' 2>/dev/null); `
149
+ + `ac_tok="v=1,cpu=$ac_pcpu,l1=\${ac_pl1:--1},mem=$ac_pmem,dsk=\${ac_pdsk:--1},vc=\${ac_pvc:-0},ram=$(( \${ac_pmt:-0} / 1024 )),ts=$(date +%s)\${ac_ptop:+,top=$ac_ptop}"; `
117
150
  + `}; `;
118
151
  }
119
152
 
@@ -184,12 +217,30 @@ export function parsePerfToken(token: string): GuestPerfSample | null {
184
217
  ramMb: bounded("ram", 1, 16 * 1024 * 1024),
185
218
  // Bounded to [2020, 2100) in epoch seconds — dedupe-grade only.
186
219
  sampledAtS: bounded("ts", 1_577_836_800, 4_102_444_800),
220
+ topProcesses: parseTopProcesses(kv.get("top")),
187
221
  };
188
222
  if (sample.cpuBusyPct === null && sample.load1 === null
189
223
  && sample.memUsedPct === null && sample.diskUsedPct === null) return null;
190
224
  return sample;
191
225
  }
192
226
 
227
+ /** The `top` field, clamped entry by entry: a name outside the alphabet or
228
+ * an RSS outside [0, 64 GB] drops THAT entry; more than the cap is cut;
229
+ * an absent or empty field is null (not reported). */
230
+ export function parseTopProcesses(raw: string | undefined): GuestTopProcess[] | null {
231
+ if (raw === undefined || raw.length === 0) return null;
232
+ const out: GuestTopProcess[] = [];
233
+ for (const entry of raw.split(";").slice(0, PERF_TOP_PROCESSES)) {
234
+ const i = entry.lastIndexOf(":");
235
+ if (i <= 0) continue;
236
+ const name = entry.slice(0, i);
237
+ const rssMb = Number(entry.slice(i + 1));
238
+ if (!TOP_NAME_RE.test(name) || !Number.isInteger(rssMb) || rssMb < 0 || rssMb > TOP_RSS_MAX_MB) continue;
239
+ out.push({ name, rssMb });
240
+ }
241
+ return out.length > 0 ? out : null;
242
+ }
243
+
193
244
  /** Parse the standalone probe's stdout (the LAST `perf ` line wins — envd
194
245
  * occasionally prepends shell noise). */
195
246
  export function parsePerfProbeOutput(stdout: string): GuestPerfSample | null {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * agent — canonical entry point for embedding an LLM agent inside a
3
- * workflow. The workflow's `run()` body calls it; the loop executes
3
+ * workflow. A step's `run()` body calls it; the loop executes
4
4
  * against the runner's own VM.
5
5
  *
6
6
  * Glue packaged so workflows don't duplicate it:
package/src/client.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ConversationRow, CreateChatInput, ChannelIvyState, ProjectIvyConnection } from "./types/api-conversations.js";
1
+ import type { ConversationRow, CreateChatInput, ChannelIvyState, DriveSharesView, IvyConnectionsView, ListingPage } from "./types/api-conversations.js";
2
2
  /**
3
3
  * AgentComposeClient — HTTP client for the agent-compose server API.
4
4
  *
@@ -34,11 +34,12 @@ import type {
34
34
  CreateCloudSessionInput, CloudSessionCreated,
35
35
  SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
36
36
  BackgroundWorkChildDecl, BackgroundWorkStatus, MachineUpsizeOutcome,
37
- SessionChangeSet, SessionMergeGated, SessionMergeReport, SessionDiscardReport,
37
+ CreateSessionWaitInput, SessionWait, SessionWaitCreated, SessionWaitList,
38
+ SessionChangeSet, SessionFilePlane, SessionMergeGated, SessionMergeReport, SessionDiscardReport,
38
39
  SessionChangePreflight, SessionRebaseReport,
39
40
  ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential,
40
41
  SendConversationMessageInput, SendConversationMessageResult,
41
- ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
42
+ ChannelSessionRow, ChannelSessionsResponse,
42
43
  SessionDirectMessageSent, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
43
44
  ConversationPresenceSnapshot, StreamConversationOptions, AgentListRow,
44
45
  SessionPerfHistoryPage,
@@ -48,9 +49,9 @@ import type {
48
49
  SetScopeGrantsInput, SetTemplateScopeInput,
49
50
  } from "./types/api-scopes.js";
50
51
  import type {
51
- Project, ProjectsPage, ProjectRole, ProjectMember,
52
- ProjectObjectsPage, ProjectAddPreview,
53
- AddProjectObjectInput, AddProjectObjectResult, RefreshProjectObjectResult,
52
+ Project, ProjectUpdate, ProjectsPage, ProjectRole, ProjectMember,
53
+ ProjectObject, ProjectObjectsPage, ProjectObjectCounts, ProjectAddPreview,
54
+ AddProjectObjectInput, AddProjectObjectResult, RefreshProjectObjectResult, ProjectStatus, ThreadStatus, MeStatus,
54
55
  } from "./types/api-projects.js";
55
56
  import type {
56
57
  RegisterResult, RegisterWorkflowInput, TemplateRow, TemplateDetail, ListTemplatesOptions,
@@ -58,7 +59,7 @@ import type {
58
59
  FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage, FactoryFileConflict, FactoryFileConflictList, ProjectSecretMeta, ProjectSecretList,
59
60
  FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
60
61
  ScheduleRow, CreateScheduleInput,
61
- SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultCatalogEntry,
62
+ SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultKeyShape, VaultCatalogEntry, RequestedVaultEntry, VaultCardHint,
62
63
  CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage, UsageResponse,
63
64
  DriveRepoLink, CreateDriveRepoLinkInput, CreateNativeRepoInput, CreateNativeRepoResult,
64
65
  DriveMountSession, CreateDriveMountSessionInput,
@@ -88,20 +89,25 @@ export type {
88
89
  } from "./types/api-runs.js";
89
90
  export type {
90
91
  TeamMember, Mention, CreateMentionsInput,
91
- CreateChatInput, ChannelIvyState, ProjectIvyConnection,
92
- ConversationMessagePart, ConversationRow, ConversationMessageRow,
92
+ CreateChatInput, ChannelIvyState, IvyConnection, HiddenIvyConnection, IvyConnectionsView, IvyConnectionScope, ListingPage,
93
+ DriveShareItem, DriveShare, HiddenDriveShare, DriveSharesView,
94
+ WorkspaceConnection, WorkspaceConnectionAudienceEntry, WorkspaceConnectionsScopeView,
95
+ ConversationMessagePart, ConversationRow, ConversationProject, ConversationMessageRow,
93
96
  ConversationsPage, SessionsPage, ConversationDetail,
94
97
  CreateCloudSessionInput, CloudSessionCreated, ConversationThread,
98
+ ConversationUnreadSummary, ConversationUnreadThreads, ConversationReadInput, ConversationReadResult,
95
99
  SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
96
100
  BackgroundWorkChildDecl, BackgroundWorkStatus, MachineUpsizeOutcome,
101
+ SessionWaitKind, SessionWaitState, CreateSessionWaitInput, SessionWait, SessionWaitCheck, SessionWaitCheckResult, SessionWaitCheckReview, SessionWaitCreated, SessionWaitList,
97
102
  SessionFileChange, SessionChangeSet, SessionChangeStats, SessionDiffReviewBound, ReviewSuggestion,
103
+ SessionFileHold, SessionFilePlane,
98
104
  ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential, SessionReviewDocument,
99
105
  ReviewSuggestionDecision, ReviewSuggestionDecisions,
100
106
  SessionMergeReportDetail, SessionMergeReport, SessionMergeGated, SessionDiscardReport,
101
107
  SessionPreflightFileRow, SessionChangePreflight, SessionRebaseReport,
102
108
  ConversationPageContext, SendConversationMessageInput, ConversationTurnState,
103
109
  SendConversationMessageResult, UnnotifiedMention, ConversationPresenceSnapshot, AgentListRow, StreamConversationOptions,
104
- ChannelSessionStatus, ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
110
+ ChannelSessionStatus, ChannelSessionRow, ChannelWorkerFacts, ChannelSessionsResponse,
105
111
  SessionDirectMessageSent, BranchClaimInfo, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
106
112
  SessionPerfBucket, SessionPerfHistoryPage,
107
113
  } from "./types/api-conversations.js";
@@ -111,10 +117,15 @@ export type {
111
117
  RunContext, SetScopeGrantsInput, SetTemplateScopeInput,
112
118
  } from "./types/api-scopes.js";
113
119
  export type {
114
- Project, ProjectsPage, ProjectRole, ProjectMember,
115
- ProjectObject, ProjectObjectsPage, ProjectSkippedFile, ProjectPreviewFile,
120
+ Project, ProjectUpdate, ProjectsPage, ProjectRole, ProjectMember,
121
+ ProjectObject, ProjectObjectsPage, ProjectObjectCounts, ProjectSkippedFile, ProjectPreviewFile,
116
122
  ProjectAddPreview, AddProjectObjectInput, AddProjectObjectResult,
117
123
  RefreshProjectObjectResult,
124
+ ProjectStatus, ProjectStatusThread, ProjectStatusTask, ProjectStatusPerson, ProjectStatusWorker, ProjectStatusActivity,
125
+ ProjectStatusActivityKind, ProjectStatusWaitingOn, ProjectStatusWaitingCount, ProjectStatusCounts,
126
+ ThreadStatus, ThreadStatusWorker, ThreadStatusWorkerState, ThreadStatusWorkerEffort, ThreadStatusTodo, ThreadStatusDirector, ThreadStatusOutput, ThreadStatusEvent, ChatWork, ChatWorkThread,
127
+ ThreadStatusWaitingOn, ThreadStatusAssignee, ThreadStatusTodoActivity, ChatTodoPatch, ChatTodoComment,
128
+ MeStatus, MeStatusCounts,
118
129
  } from "./types/api-projects.js";
119
130
  export type {
120
131
  RegisterResult, RegisteredRuntime, RuntimeSourceInput, TemplateSourceRef, RegisterWorkflowInput,
@@ -124,7 +135,7 @@ export type {
124
135
  PublicFileLinkState,
125
136
  FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
126
137
  ScheduleRow, CreateScheduleInput,
127
- SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultCatalogEntry,
138
+ SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultKeyShape, VaultCatalogEntry, RequestedVaultEntry, VaultCardHint,
128
139
  CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage,
129
140
  UsageRollupRow, UsageResponse,
130
141
  DriveRepoLink, CreateDriveRepoLinkInput, CreateNativeRepoInput, CreateNativeRepoResult,
@@ -157,6 +168,16 @@ function detectAmbientParentRunId(): string | null {
157
168
  * URL-encoded as defense-in-depth: today's server-side patterns
158
169
  * (`^[a-z][a-z0-9-]*$`) can't produce reserved characters, but a future
159
170
  * relaxation would otherwise quietly become an injection vector. */
171
+ /** The query string of one page of a scope listing (`cursor`: the previous
172
+ * page's `nextCursor`; `limit`: 1..200). Empty for the first page at the
173
+ * server's size. */
174
+ function listingPageQuery(page: ListingPage = {}): string {
175
+ const q = new URLSearchParams();
176
+ if (page.cursor) q.set("cursor", page.cursor);
177
+ if (page.limit !== undefined) q.set("limit", String(page.limit));
178
+ return q.toString() ? `?${q}` : "";
179
+ }
180
+
160
181
  function templatePath(factorySlug: string, ...rest: string[]): string {
161
182
  const tail = rest.length > 0
162
183
  ? "/" + rest.map(encodeURIComponent).join("/")
@@ -867,7 +888,7 @@ export class AgentComposeClient {
867
888
  // ── User drive mounts ──────────────────────────────────────────────────────
868
889
  // `agentc files mount` on a human's own machine: the server forks a user
869
890
  // branch, backs it with a mount session (the review surface), and mints a
870
- // user-principal gateway token. Human-held credentials only (sign-in
891
+ // user-principal gateway token. Own-device credentials only (sign-in
871
892
  // cookie or a device-flow bridge key) — sandbox session keys are refused.
872
893
 
873
894
  /** Create a local drive mount (or re-mint an existing one's token by
@@ -922,8 +943,18 @@ export class AgentComposeClient {
922
943
  getChannelIvy(id: string): Promise<ChannelIvyState> {
923
944
  return this.fetch(`/api/v1/conversations/${encodeURIComponent(id)}/ivy`);
924
945
  }
925
- listProjectIvyConnections(id: string): Promise<{ connections: ProjectIvyConnection[]; canManage: boolean }> {
926
- return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/ivy-connections`);
946
+ /** A project's GitHub grants: its own, and the workspace's it inherits,
947
+ * one page at a time (pass the previous page's `nextCursor`). A
948
+ * project's access is its members' (and the workspace admins'): a
949
+ * member of the workspace who can see a public project without being in
950
+ * it gets a 403 `project_member_required`. */
951
+ listProjectIvyConnections(id: string, page?: ListingPage): Promise<IvyConnectionsView> {
952
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/ivy-connections${listingPageQuery(page)}`);
953
+ }
954
+ /** A project's Drive shares: its own, and the workspace's it inherits,
955
+ * one page at a time. Members only, as listProjectIvyConnections. */
956
+ listProjectDriveShares(id: string, page?: ListingPage): Promise<DriveSharesView> {
957
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/drive-shares${listingPageQuery(page)}`);
927
958
  }
928
959
 
929
960
  /** List conversations the caller can access, newest-activity first.
@@ -1053,25 +1084,6 @@ export class AgentComposeClient {
1053
1084
  );
1054
1085
  }
1055
1086
 
1056
- /** Post one message into a channel AS the calling session (ADR-0057 Seam
1057
- * 4) — progress, results, questions, attributed to the session (title +
1058
- * runtime mark). Session toolbelt credential ONLY (the calling session
1059
- * resolves from the key server-side — 403 `session_credential_required`
1060
- * otherwise); the session must be attached (409 `not_attached`). By
1061
- * default the post lands in the thread of the message that last
1062
- * addressed the session from that channel (the room when none);
1063
- * `threadRootId` overrides it — must be a root message in the channel
1064
- * (400 `invalid_thread_root`). The post never triggers any turn. */
1065
- postSessionChannelMessage(
1066
- channelId: string,
1067
- input: { text: string; threadRootId?: string },
1068
- ): Promise<SessionChannelMessagePosted> {
1069
- return this.fetch<SessionChannelMessagePosted>(
1070
- `/api/v1/conversations/${encodeURIComponent(channelId)}/session-messages`,
1071
- { method: "POST", body: { text: input.text, ...(input.threadRootId ? { threadRootId: input.threadRootId } : {}) } },
1072
- );
1073
- }
1074
-
1075
1087
  /** Message ANOTHER session's conversation AS the calling session
1076
1088
  * (`agentc session message @alias`). Session toolbelt credential ONLY —
1077
1089
  * the sender resolves from the key. `target` is an @alias (team-scoped)
@@ -1178,6 +1190,32 @@ export class AgentComposeClient {
1178
1190
  );
1179
1191
  }
1180
1192
 
1193
+ // ── Session waits (owner ruling 2026-09-23: no watch runs inside a session
1194
+ // sandbox). The session declares what it waits for and ends its turn; the
1195
+ // platform senses the arrival and wakes it. Own-session key posture. ─────
1196
+
1197
+ /** Declare one wait (`agentc wait`): `POST /sessions/:id/waits`. */
1198
+ createSessionWait(conversationId: string, input: CreateSessionWaitInput): Promise<SessionWaitCreated> {
1199
+ return this.fetch<SessionWaitCreated>(
1200
+ `/api/v1/sessions/${encodeURIComponent(conversationId)}/waits`,
1201
+ { method: "POST", body: input },
1202
+ );
1203
+ }
1204
+
1205
+ /** This session's waits, newest first; open only unless `all`. */
1206
+ listSessionWaits(conversationId: string, opts: { all?: boolean } = {}): Promise<SessionWaitList> {
1207
+ const q = opts.all ? "?all=1" : "";
1208
+ return this.fetch<SessionWaitList>(`/api/v1/sessions/${encodeURIComponent(conversationId)}/waits${q}`);
1209
+ }
1210
+
1211
+ /** Cancel an open wait (`agentc wait cancel`): open -> cancelled. */
1212
+ cancelSessionWait(conversationId: string, waitId: string): Promise<SessionWait> {
1213
+ return this.fetch<SessionWait>(
1214
+ `/api/v1/sessions/${encodeURIComponent(conversationId)}/waits/${encodeURIComponent(waitId)}`,
1215
+ { method: "DELETE" },
1216
+ );
1217
+ }
1218
+
1181
1219
  /** Request ONE machine size up for a cloud session (task #110 — the
1182
1220
  * auto-resize policy's agent door). The platform arbitrates: within the
1183
1221
  * team's daily cap the upsize is auto-granted (and executes the moment
@@ -1277,17 +1315,30 @@ export class AgentComposeClient {
1277
1315
  return new Uint8Array(buf);
1278
1316
  }
1279
1317
 
1318
+ /** Where ONE path's current version lives for a session
1319
+ * (`GET /conversations/:id/changes/file`): `main` when the main drive
1320
+ * holds the session branch's version (landed, or never changed on the
1321
+ * branch), else `branch` with `why` naming what keeps it off main. The
1322
+ * answer a `file:<path>?session=<id>` link resolves when it is opened.
1323
+ * 404 = missing path or no access (uniform, no existence oracle). */
1324
+ getSessionFilePlane(conversationId: string, path: string): Promise<SessionFilePlane> {
1325
+ const q = new URLSearchParams({ path });
1326
+ return this.fetch<SessionFilePlane>(
1327
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/file?${q}`,
1328
+ );
1329
+ }
1330
+
1280
1331
  /** Publish a diff review's WHOLE document — prose notes plus structured
1281
1332
  * suggestions, FULL REPLACE on every call (`POST
1282
1333
  * /conversations/:id/review-notes`; `agentc review publish`). Callable
1283
- * ONLY with the bound review session's own toolbelt credential — any
1284
- * other key (another session, a human cookie, a plain API key) refuses
1285
- * 403. `conversationId` is the REVIEWED session's conversation (in a
1286
- * review sandbox: `AGENT_COMPOSE_REVIEW_OF_CONVERSATION_ID`).
1334
+ * ONLY with the toolbelt credential of a review session spawned for this
1335
+ * diff, which replaces its own document — any other key (another
1336
+ * session, a human cookie, a plain API key) refuses 403.
1337
+ * `conversationId` is the REVIEWED session's conversation (in a review
1338
+ * sandbox: `AGENT_COMPOSE_REVIEW_OF_CONVERSATION_ID`).
1287
1339
  *
1288
- * Failures: 403 `session_credential_required` | `not_review_session` |
1289
- * `review_superseded` (a newer review replaced this one); 400
1290
- * `suggestion_path_not_in_change_set` (naming the offenders); 409
1340
+ * Failures: 403 `session_credential_required` | `not_review_session`;
1341
+ * 400 `suggestion_path_not_in_change_set` (naming the offenders); 409
1291
1342
  * `review_disabled`. */
1292
1343
  publishReviewNotes(
1293
1344
  conversationId: string,
@@ -1323,22 +1374,6 @@ export class AgentComposeClient {
1323
1374
  );
1324
1375
  }
1325
1376
 
1326
- /** Mint ONE fresh short-lived WIP-push credential for the CALLING
1327
- * session's git namespace (`POST /sessions/self/wip-git-credential`,
1328
- * git-door slice 2) — the server half of `agentc repos clone` and its
1329
- * git credential helper. The token's Push grant is exactly
1330
- * `refs/sessions/<sessionId>/`; the door refuses everything else.
1331
- * Callable only with a session's own toolbelt credential.
1332
- *
1333
- * Failures: 403 `session_credential_required` | `not_a_session`;
1334
- * 409 `git_remote_unavailable` (unbranched / legacy plane / fsgw off). */
1335
- getSessionWipGitCredential(): Promise<ReviewGitCredential & { pushNamespace: string }> {
1336
- return this.fetch<ReviewGitCredential & { pushNamespace: string }>(
1337
- "/api/v1/sessions/self/wip-git-credential",
1338
- { method: "POST" },
1339
- );
1340
- }
1341
-
1342
1377
  /** Mint a short-lived native-git credential for a linked repo's MIRROR
1343
1378
  * namespace (`POST /:slug/repo-links/:id/git-credential`, git-door
1344
1379
  * slice 4). mode "fetch" (default) reads the mirror — gated on link
@@ -1485,8 +1520,10 @@ export class AgentComposeClient {
1485
1520
  return body.members;
1486
1521
  }
1487
1522
 
1488
- /** Remove a member (conversation `owner` or team admin only).
1489
- * Throws 400 `last_owner` when the target is the roster's last owner. */
1523
+ /** Remove a member (conversation `owner` or team admin), or leave the
1524
+ * chat yourself: `userId` "me" (or your own id) is the self-leave door.
1525
+ * Throws 400 `last_owner` when the target is the roster's last owner and
1526
+ * 400 `creator_cannot_leave` when the chat's creator tries to leave it. */
1490
1527
  async removeConversationMember(conversationId: string, userId: string): Promise<ConversationMember[]> {
1491
1528
  const body = await this.fetch<{ members: ConversationMember[] }>(
1492
1529
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/members/${encodeURIComponent(userId)}`,
@@ -1838,9 +1875,16 @@ export class AgentComposeClient {
1838
1875
 
1839
1876
  /** Rename a project (requires `write` role). */
1840
1877
  async renameProject(id: string, name: string): Promise<Project> {
1878
+ return this.updateProject(id, { name });
1879
+ }
1880
+
1881
+ /** Change a project's name, tier, description or dates (one PATCH; see
1882
+ * ProjectUpdate for who may set what). 400 `date_order` when the target
1883
+ * date would come before the start date. */
1884
+ async updateProject(id: string, patch: ProjectUpdate): Promise<Project> {
1841
1885
  const body = await this.fetch<{ project: Project }>(
1842
1886
  `/api/v1/projects/${encodeURIComponent(id)}`,
1843
- { method: "PATCH", body: { name } },
1887
+ { method: "PATCH", body: patch },
1844
1888
  );
1845
1889
  return body.project;
1846
1890
  }
@@ -1874,7 +1918,11 @@ export class AgentComposeClient {
1874
1918
  return body.members;
1875
1919
  }
1876
1920
 
1877
- /** Remove a member — project owner/team admin, or self-leave. Throws 400
1921
+ /** Remove a member — project owner/team admin, or self-leave (`me`).
1922
+ * Leaving a project takes its chats with it: the person loses their row
1923
+ * in every chat filed in the project, public and private, except a chat
1924
+ * they reach through another project they remain in, the home room, a
1925
+ * chat they created, and a chat they are the last owner of. Throws 400
1878
1926
  * `last_owner` when the target is the roster's last owner. */
1879
1927
  async removeProjectMember(id: string, userId: string): Promise<ProjectMember[]> {
1880
1928
  const body = await this.fetch<{ members: ProjectMember[] }>(
@@ -1884,6 +1932,14 @@ export class AgentComposeClient {
1884
1932
  return body.members;
1885
1933
  }
1886
1934
 
1935
+ /** What leaving the project would take from the caller: the chats they
1936
+ * lose with it, and the project's shared files and folders. */
1937
+ async leaveProjectPreview(id: string): Promise<{ chats: number; files: number }> {
1938
+ return this.fetch<{ chats: number; files: number }>(
1939
+ `/api/v1/projects/${encodeURIComponent(id)}/leave-preview`,
1940
+ );
1941
+ }
1942
+
1887
1943
  /** Change a member's role (project owner or team admin). Throws 400
1888
1944
  * `last_owner` when demoting the roster's last owner. */
1889
1945
  async setProjectMemberRole(
@@ -1898,15 +1954,56 @@ export class AgentComposeClient {
1898
1954
  return body.members;
1899
1955
  }
1900
1956
 
1901
- /** List a project's objects (member only), newest first. Cursor-paginated. */
1902
- listProjectObjects(id: string, opts?: { cursor?: string }): Promise<ProjectObjectsPage> {
1957
+ /** List a project's objects (member only), newest first. Cursor-paginated.
1958
+ * `type` narrows to one object kind at the server, under the same
1959
+ * cursor — `"conversation"` is a project's chats alone, however many
1960
+ * files it holds. */
1961
+ listProjectObjects(id: string, opts?: { cursor?: string; type?: ProjectObject["type"] }): Promise<ProjectObjectsPage> {
1903
1962
  const q = new URLSearchParams();
1904
1963
  if (opts?.cursor) q.set("cursor", opts.cursor);
1964
+ if (opts?.type) q.set("type", opts.type);
1905
1965
  return this.fetch<ProjectObjectsPage>(
1906
1966
  `/api/v1/projects/${encodeURIComponent(id)}/objects${q.toString() ? `?${q}` : ""}`,
1907
1967
  );
1908
1968
  }
1909
1969
 
1970
+ /** How many objects of each kind the project holds for the caller
1971
+ * (member only): the whole project's counts, where a page of
1972
+ * `listProjectObjects` holds at most fifty. */
1973
+ getProjectObjectCounts(id: string): Promise<ProjectObjectCounts> {
1974
+ return this.fetch<ProjectObjectCounts>(`/api/v1/projects/${encodeURIComponent(id)}/objects/counts`);
1975
+ }
1976
+
1977
+ /** The project status view (member only): its outcome threads with their
1978
+ * plans, workers, people and next dates, the project's progress, finish
1979
+ * line and next check-in, and what moved lately. `threads` is
1980
+ * cursor-paginated; `recentlyClosed` rides the first page. */
1981
+ getProjectStatus(id: string, opts?: { cursor?: string; limit?: number }): Promise<ProjectStatus> {
1982
+ const q = new URLSearchParams();
1983
+ if (opts?.cursor) q.set("cursor", opts.cursor);
1984
+ if (opts?.limit) q.set("limit", String(opts.limit));
1985
+ return this.fetch<ProjectStatus>(
1986
+ `/api/v1/projects/${encodeURIComponent(id)}/status${q.toString() ? `?${q}` : ""}`,
1987
+ );
1988
+ }
1989
+
1990
+ /** One thread's status for the inline expansion of a status row: its
1991
+ * summary, workers by state, to-dos and last events. 404 for a thread
1992
+ * the caller cannot read. */
1993
+ getThreadStatus(threadId: string): Promise<ThreadStatus> {
1994
+ return this.fetch<ThreadStatus>(`/api/v1/threads/${encodeURIComponent(threadId)}/status`);
1995
+ }
1996
+
1997
+ /** The caller's personal status in the current workspace: the threads
1998
+ * relevant to them (posted, mentioned, asked, a to-do, or in the chat),
1999
+ * grouped client-side by project. `threads` is cursor-paginated. */
2000
+ getMeStatus(opts?: { cursor?: string; limit?: number }): Promise<MeStatus> {
2001
+ const q = new URLSearchParams();
2002
+ if (opts?.cursor) q.set("cursor", opts.cursor);
2003
+ if (opts?.limit) q.set("limit", String(opts.limit));
2004
+ return this.fetch<MeStatus>(`/api/v1/me/status${q.toString() ? `?${q}` : ""}`);
2005
+ }
2006
+
1910
2007
  /** DRY-RUN preview of a session add: the files that WOULD be shared (from
1911
2008
  * the caller's owned scopes) + any owned-but-skipped entries (paths
1912
2009
  * concealed from the caller are omitted). Powers the consent dialog. Same
@@ -2083,9 +2180,10 @@ export class AgentComposeClient {
2083
2180
  { method: "POST", body: input });
2084
2181
  }
2085
2182
 
2086
- /** The team's connector grants (metadata only — never tokens). Used by
2087
- * `agentc repos link` to default `--grant` when the team holds exactly
2088
- * one active GitHub grant. */
2183
+ /** The caller's own connector grants and the workspace's GitHub
2184
+ * installations (metadata only — never tokens; a teammate's other
2185
+ * accounts never come back). Used by `agentc repos link` to default
2186
+ * `--grant` when the team holds exactly one active GitHub grant. */
2089
2187
  async listConnectorGrants(): Promise<ConnectorGrantSummary[]> {
2090
2188
  const body = await this.fetch<{ data: ConnectorGrantSummary[] }>("/api/v1/connectors");
2091
2189
  return body.data;
@@ -2164,7 +2262,16 @@ export class AgentComposeClient {
2164
2262
  * writer. */
2165
2263
  requestSessionSecrets(
2166
2264
  conversationId: string, keys: string[],
2167
- opts?: { reason?: string; setLabel?: string; kind?: VaultRequestKind },
2265
+ opts?: {
2266
+ reason?: string; setLabel?: string; kind?: VaultRequestKind; shapes?: Record<string, VaultKeyShape>;
2267
+ savedEntry?: RequestedVaultEntry;
2268
+ /** A REPLACEMENT: the keys are held already and did not work; the card
2269
+ * asks for a new value of the same key. */
2270
+ replace?: boolean;
2271
+ /** A different credential of a kind the work already holds under
2272
+ * another name (the server refuses such an ask without it). */
2273
+ distinct?: boolean;
2274
+ },
2168
2275
  ): Promise<SessionSecretRequestCreated> {
2169
2276
  return this.fetch(
2170
2277
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests`,
@@ -2176,6 +2283,14 @@ export class AgentComposeClient {
2176
2283
  // WHAT KIND of credential — the vault page leads with the user's
2177
2284
  // matching STANDING entries (personal-vault reuse, 2026-08-31).
2178
2285
  ...(opts?.kind ? { kind: opts.kind } : {}),
2286
+ // WHICH standing entry — by label and/or a card's last four — so the
2287
+ // card and the vault page lead with that exact one.
2288
+ ...(opts?.savedEntry ? { savedEntry: opts.savedEntry } : {}),
2289
+ // Per-key DELIVERY (brokered onto a host, or injected) — required
2290
+ // for every key of an api_key ask.
2291
+ ...(opts?.shapes ? { shapes: opts.shapes } : {}),
2292
+ ...(opts?.replace ? { replace: true } : {}),
2293
+ ...(opts?.distinct ? { distinct: true } : {}),
2179
2294
  } });
2180
2295
  }
2181
2296
 
@@ -2183,8 +2298,11 @@ export class AgentComposeClient {
2183
2298
  * could grant to this session — labels, kinds, field NAMES, last-used.
2184
2299
  * Values are write-only and never on this wire. Query it BEFORE asking
2185
2300
  * the human for a credential: a matching entry means the ask is a GRANT
2186
- * ask naming it, never a blank re-entry form. */
2187
- getSessionVaultCatalog(conversationId: string): Promise<{ entries: VaultCatalogEntry[]; note?: string }> {
2301
+ * ask naming it, never a blank re-entry form. For the session's own key
2302
+ * it also carries `reaches`: what the session already reaches with no
2303
+ * credential asked for (`agentc access`), so a need met there is never
2304
+ * a vault ask. */
2305
+ getSessionVaultCatalog(conversationId: string): Promise<{ entries: VaultCatalogEntry[]; note?: string; reaches?: string }> {
2188
2306
  return this.fetch(
2189
2307
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/vault-catalog`);
2190
2308
  }
@@ -2192,7 +2310,7 @@ export class AgentComposeClient {
2192
2310
  /** Poll a vault link's status (`pending` → `fulfilled`; `expired` when the
2193
2311
  * clock ran out; `cancelled` when the human denied it or the requester
2194
2312
  * withdrew it — attribution rides the answer). The in-sandbox
2195
- * `agentc secrets request --wait` loop. */
2313
+ * `agentc secrets session request --wait` loop. */
2196
2314
  getSessionSecretRequest(conversationId: string, requestId: string): Promise<SessionSecretRequestStatus> {
2197
2315
  return this.fetch(
2198
2316
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests/${encodeURIComponent(requestId)}`);
@@ -2224,6 +2342,8 @@ export class AgentComposeClient {
2224
2342
  /** Create a new API key on the caller's team. The plaintext `key` is
2225
2343
  * returned once — it cannot be retrieved later.
2226
2344
  *
2345
+ * `scopes` omitted mints `read` alone; every scope asked for must be one
2346
+ * the caller holds (a key carries no more than whoever mints it).
2227
2347
  * When `factorySlug` is set, the new key is restricted to that factory.
2228
2348
  * Factory-scoped keys can only mint other keys bound to the same factory. */
2229
2349
  createApiKey(input: CreateApiKeyInput): Promise<ApiKeyCreated> {
package/src/directives.ts CHANGED
@@ -62,9 +62,9 @@ export type CloudDirective =
62
62
  * the mentions ping are server-authoritative (`display_ask` shape). The
63
63
  * one write-bearing directive (see the module header). */
64
64
  | { kind: "ask"; prompt: string; approver: string; options?: Array<{ id: string; label: string }> }
65
- /** Offer the human the desktop takeover door (`request_desktop_takeover`
66
- * shape): the server captures the current screen and posts a card with a
67
- * signed takeover link — "You can take over from here." Grants nothing
65
+ /** Offer the human the desktop takeover door (`show_computer` handing the
66
+ * screen over): the server captures the current screen and posts a card
67
+ * with a signed takeover link — "You can take over from here." Grants nothing
68
68
  * by itself: the link's viewer must still be a signed-in member with
69
69
  * write access to THIS session (the server re-checks on every resolve).
70
70
  * ONLY for structurally-human walls (a CAPTCHA, human-only verification)
package/src/display.ts CHANGED
@@ -681,6 +681,18 @@ function runIdIn(text: string): { runId: string; parsed: Record<string, unknown>
681
681
  return m ? { runId: m[1], parsed: null } : null;
682
682
  }
683
683
 
684
+ /** Every platform tool name `createDisplayPromoter` can put on a promoted
685
+ * `tool_call` — the closed set a local bridge daemon may legitimately post
686
+ * under a platform-owned name. The server's part-POST door
687
+ * (routes/bridge.ts) refuses every other platform tool name from a daemon,
688
+ * so a local agent cannot forge an approval, vault or takeover card. Kept
689
+ * beside the promoter so the two cannot drift. */
690
+ export const DISPLAY_PROMOTED_TOOL_NAMES: ReadonlySet<string> = new Set([
691
+ "display_run", "display_changes", "display_document", "display_plan", "display_image",
692
+ "display_desktop", "display_preview", "display_table", "display_chart", "display_diff",
693
+ "display_ask", "dispatch_workflow",
694
+ ]);
695
+
684
696
  export function createDisplayPromoter(ctx: DisplayPromoterContext = {}): DisplayPromoter {
685
697
  const pending = new Map<string, PendingPromotion>();
686
698
 
package/src/errors.ts CHANGED
@@ -7,6 +7,7 @@ export type AuthzErrorCode =
7
7
  | "capability_required"
8
8
  | "membership_required"
9
9
  | "last_owner"
10
+ | "creator_cannot_leave"
10
11
  | "approver_only";
11
12
 
12
13
  /** Thrown by AgentComposeClient when the server returns a non-2xx response. */