@agent-compose/sdk 0.8.5 → 0.8.7

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 (100) 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 +6 -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 +119 -54
  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 +771 -204
  13. package/dist/request-context/request-context.d.ts +1 -1
  14. package/dist/runtimes/_cli-agent.d.ts +185 -68
  15. package/dist/runtimes/_reported-model.d.ts +16 -0
  16. package/dist/runtimes/claude-code.d.ts +60 -1
  17. package/dist/runtimes/claude.d.ts +1 -1
  18. package/dist/runtimes/codex.d.ts +94 -6
  19. package/dist/runtimes/codex.mid-turn-hook.test.d.ts +10 -0
  20. package/dist/runtimes/model-report.test.d.ts +14 -0
  21. package/dist/runtimes/openai-desktop.js +741 -200
  22. package/dist/runtimes/opencode.d.ts +48 -11
  23. package/dist/runtimes/opencode.test.d.ts +14 -0
  24. package/dist/sandbox/baked-clis.d.ts +75 -0
  25. package/dist/sandbox/exec-stream.d.ts +1 -2
  26. package/dist/sandbox/network-policy.d.ts +23 -5
  27. package/dist/sandbox.d.ts +4 -2
  28. package/dist/step-invocation/protocol.d.ts +3 -4
  29. package/dist/step-invocation/server.d.ts +2 -2
  30. package/dist/step-invocation/types.d.ts +1 -1
  31. package/dist/types/api-conversations.d.ts +442 -29
  32. package/dist/types/api-factory.d.ts +99 -10
  33. package/dist/types/api-projects.d.ts +521 -0
  34. package/dist/types/api-runs.d.ts +83 -0
  35. package/dist/types/api-scopes.d.ts +32 -3
  36. package/dist/types/conversation-stream.d.ts +5 -0
  37. package/dist/types/execution-context.d.ts +1 -1
  38. package/dist/types/protocol.d.ts +86 -2
  39. package/dist/types/runtime.d.ts +9 -2
  40. package/dist/types/workflow-metadata.d.ts +2 -4
  41. package/dist/types/workflow-plan.d.ts +1 -3
  42. package/dist/utils/bundler.d.ts +23 -0
  43. package/dist/workflow-steps/observability.d.ts +2 -3
  44. package/dist/workflow-steps/runner.d.ts +5 -8
  45. package/dist/workflow-steps/types.d.ts +8 -10
  46. package/dist/workflow-steps/workflow.d.ts +2 -1
  47. package/dist/workflows/engine.d.ts +3 -5
  48. package/dist/workflows/invoke-child.d.ts +2 -2
  49. package/package.json +2 -2
  50. package/src/agent/agent-context.ts +168 -125
  51. package/src/agent/agent-loop.ts +7 -6
  52. package/src/agent/perf-sampler.ts +54 -3
  53. package/src/agent/run-agent.ts +1 -1
  54. package/src/client.ts +226 -71
  55. package/src/directives.ts +3 -3
  56. package/src/display.ts +12 -0
  57. package/src/errors.ts +1 -0
  58. package/src/generated/agentc-commands.ts +571 -0
  59. package/src/index.ts +57 -21
  60. package/src/pause/pause-core.ts +2 -1
  61. package/src/request-context/request-context.ts +1 -1
  62. package/src/runtimes/_cli-agent.ts +318 -122
  63. package/src/runtimes/_reported-model.ts +24 -0
  64. package/src/runtimes/claude-code.ts +195 -12
  65. package/src/runtimes/claude.ts +9 -2
  66. package/src/runtimes/codex.ts +188 -19
  67. package/src/runtimes/opencode.ts +195 -26
  68. package/src/sandbox/baked-clis.ts +86 -0
  69. package/src/sandbox/exec-stream.ts +1 -2
  70. package/src/sandbox/network-policy.ts +51 -7
  71. package/src/sandbox/providers/e2b.ts +3 -3
  72. package/src/sandbox/providers/vercel.ts +6 -6
  73. package/src/sandbox.ts +8 -2
  74. package/src/step-invocation/invoker.ts +2 -6
  75. package/src/step-invocation/protocol.ts +3 -4
  76. package/src/step-invocation/server.ts +2 -2
  77. package/src/types/api-conversations.ts +366 -23
  78. package/src/types/api-factory.ts +95 -10
  79. package/src/types/api-projects.ts +477 -0
  80. package/src/types/api-runs.ts +73 -0
  81. package/src/types/api-scopes.ts +32 -3
  82. package/src/types/conversation-stream.ts +5 -0
  83. package/src/types/execution-context.ts +1 -1
  84. package/src/types/protocol.ts +91 -2
  85. package/src/types/runtime.ts +8 -2
  86. package/src/types/sandbox-environment.ts +1 -2
  87. package/src/types/workflow-metadata.ts +2 -4
  88. package/src/types/workflow-plan.ts +1 -3
  89. package/src/utils/bundler.ts +88 -19
  90. package/src/workflow-steps/observability.ts +2 -3
  91. package/src/workflow-steps/runner.ts +5 -8
  92. package/src/workflow-steps/types.ts +8 -10
  93. package/src/workflow-steps/workflow.ts +2 -1
  94. package/src/workflows/engine.ts +3 -5
  95. package/src/workflows/invoke-child.ts +2 -2
  96. package/dist/generated/verb-synopsis.d.ts +0 -34
  97. package/dist/pause/__tests__/errors.test.d.ts +0 -1
  98. package/dist/pause/__tests__/wrappers.test.d.ts +0 -1
  99. package/dist/step-invocation/__tests__/protocol.test.d.ts +0 -1
  100. 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
  *
@@ -25,6 +25,7 @@ import type {
25
25
  RequestAgentPauseOptions, RequestAgentPauseResponse,
26
26
  SendAgentMessageOptions, SendAgentMessageResponse,
27
27
  RunDetail, RunListEntry, ListRunsOptions, TimelineEvent, RunFundingResponse, RunStepUsageResponse, EventRow, ReportEventInput,
28
+ ListWorkflowActivityOptions, ListWorkflowRunsOptions, WorkflowActivityPage, WorkflowRunsPage, WorkflowActivityHead, WorkflowActivityRun,
28
29
  ListEventsOptions, ListEventsResult, RunArtifactRow, RunLogLine, ListRunLogsOptions,
29
30
  CancelRunResponse, ListSnapshotsOptions, SnapshotListResponse, SnapshotListEntry, RunSnapshotEntry,
30
31
  } from "./types/api-runs.js";
@@ -34,11 +35,12 @@ import type {
34
35
  CreateCloudSessionInput, CloudSessionCreated,
35
36
  SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
36
37
  BackgroundWorkChildDecl, BackgroundWorkStatus, MachineUpsizeOutcome,
37
- SessionChangeSet, SessionMergeGated, SessionMergeReport, SessionDiscardReport,
38
+ CreateSessionWaitInput, SessionWait, SessionWaitCreated, SessionWaitList,
39
+ SessionChangeSet, SessionFilePlane, SessionMergeGated, SessionMergeReport, SessionDiscardReport,
38
40
  SessionChangePreflight, SessionRebaseReport,
39
41
  ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential,
40
42
  SendConversationMessageInput, SendConversationMessageResult,
41
- ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
43
+ ChannelSessionRow, ChannelSessionsResponse,
42
44
  SessionDirectMessageSent, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
43
45
  ConversationPresenceSnapshot, StreamConversationOptions, AgentListRow,
44
46
  SessionPerfHistoryPage,
@@ -48,9 +50,9 @@ import type {
48
50
  SetScopeGrantsInput, SetTemplateScopeInput,
49
51
  } from "./types/api-scopes.js";
50
52
  import type {
51
- Project, ProjectsPage, ProjectRole, ProjectMember,
52
- ProjectObjectsPage, ProjectAddPreview,
53
- AddProjectObjectInput, AddProjectObjectResult, RefreshProjectObjectResult,
53
+ Project, ProjectUpdate, ProjectsPage, ProjectRole, ProjectMember,
54
+ ProjectObject, ProjectObjectsPage, ProjectObjectCounts, ProjectAddPreview,
55
+ AddProjectObjectInput, AddProjectObjectResult, RefreshProjectObjectResult, ProjectStatus, ThreadStatus, MeStatus,
54
56
  } from "./types/api-projects.js";
55
57
  import type {
56
58
  RegisterResult, RegisterWorkflowInput, TemplateRow, TemplateDetail, ListTemplatesOptions,
@@ -58,7 +60,7 @@ import type {
58
60
  FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage, FactoryFileConflict, FactoryFileConflictList, ProjectSecretMeta, ProjectSecretList,
59
61
  FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
60
62
  ScheduleRow, CreateScheduleInput,
61
- SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultCatalogEntry,
63
+ SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultKeyShape, VaultCatalogEntry, RequestedVaultEntry, VaultCardHint,
62
64
  CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage, UsageResponse,
63
65
  DriveRepoLink, CreateDriveRepoLinkInput, CreateNativeRepoInput, CreateNativeRepoResult,
64
66
  DriveMountSession, CreateDriveMountSessionInput,
@@ -80,6 +82,8 @@ export type {
80
82
  ResumePauseOptions, RequestAgentPauseOptions, AnswerSteerOptions, RequestAgentPauseResponse,
81
83
  SendAgentMessageOptions, SendAgentMessageResponse,
82
84
  RunDetail, RunListEntry, ListRunsOptions, TimelineEvent,
85
+ RunStartedBy, WorkflowActivityRun, WorkflowActivityHead, ListWorkflowActivityOptions, ListWorkflowRunsOptions,
86
+ WorkflowActivityPage, WorkflowRunsPage,
83
87
  FundingLane, RunFundingStamp, RunFundingUsageRow, RunFundingResponse,
84
88
  RunStepUsage, RunStepUsageResponse,
85
89
  EventSubjectType, EventRow, RunArtifactRow, ReportEventInput, ListEventsOptions, ListEventsResult,
@@ -88,20 +92,25 @@ export type {
88
92
  } from "./types/api-runs.js";
89
93
  export type {
90
94
  TeamMember, Mention, CreateMentionsInput,
91
- CreateChatInput, ChannelIvyState, ProjectIvyConnection,
92
- ConversationMessagePart, ConversationRow, ConversationMessageRow,
95
+ CreateChatInput, ChannelIvyState, IvyConnection, HiddenIvyConnection, IvyConnectionsView, IvyConnectionScope, ListingPage,
96
+ DriveShareItem, DriveShare, HiddenDriveShare, DriveSharesView,
97
+ WorkspaceConnection, WorkspaceConnectionAudienceEntry, WorkspaceConnectionsScopeView,
98
+ ConversationMessagePart, ConversationRow, ConversationProject, ConversationMessageRow,
93
99
  ConversationsPage, SessionsPage, ConversationDetail,
94
100
  CreateCloudSessionInput, CloudSessionCreated, ConversationThread,
101
+ ConversationUnreadSummary, ConversationUnreadThreads, ConversationReadInput, ConversationReadResult,
95
102
  SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
96
103
  BackgroundWorkChildDecl, BackgroundWorkStatus, MachineUpsizeOutcome,
104
+ SessionWaitKind, SessionWaitState, CreateSessionWaitInput, SessionWait, SessionWaitCheck, SessionWaitCheckResult, SessionWaitCheckReview, SessionWaitCreated, SessionWaitList,
97
105
  SessionFileChange, SessionChangeSet, SessionChangeStats, SessionDiffReviewBound, ReviewSuggestion,
106
+ SessionFileHold, SessionFilePlane,
98
107
  ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential, SessionReviewDocument,
99
108
  ReviewSuggestionDecision, ReviewSuggestionDecisions,
100
109
  SessionMergeReportDetail, SessionMergeReport, SessionMergeGated, SessionDiscardReport,
101
110
  SessionPreflightFileRow, SessionChangePreflight, SessionRebaseReport,
102
111
  ConversationPageContext, SendConversationMessageInput, ConversationTurnState,
103
112
  SendConversationMessageResult, UnnotifiedMention, ConversationPresenceSnapshot, AgentListRow, StreamConversationOptions,
104
- ChannelSessionStatus, ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
113
+ ChannelSessionStatus, ChannelSessionRow, ChannelWorkerFacts, ChannelSessionsResponse,
105
114
  SessionDirectMessageSent, BranchClaimInfo, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
106
115
  SessionPerfBucket, SessionPerfHistoryPage,
107
116
  } from "./types/api-conversations.js";
@@ -111,10 +120,15 @@ export type {
111
120
  RunContext, SetScopeGrantsInput, SetTemplateScopeInput,
112
121
  } from "./types/api-scopes.js";
113
122
  export type {
114
- Project, ProjectsPage, ProjectRole, ProjectMember,
115
- ProjectObject, ProjectObjectsPage, ProjectSkippedFile, ProjectPreviewFile,
123
+ Project, ProjectUpdate, ProjectsPage, ProjectRole, ProjectMember,
124
+ ProjectObject, ProjectObjectsPage, ProjectObjectCounts, ProjectSkippedFile, ProjectPreviewFile,
116
125
  ProjectAddPreview, AddProjectObjectInput, AddProjectObjectResult,
117
126
  RefreshProjectObjectResult,
127
+ ProjectStatus, ProjectStatusThread, ProjectStatusTask, ProjectStatusPerson, ProjectStatusWorker, ProjectStatusActivity,
128
+ ProjectStatusActivityKind, ProjectStatusWaitingOn, ProjectStatusWaitingCount, ProjectStatusCounts,
129
+ ThreadStatus, ThreadStatusWorker, ThreadStatusWorkerState, ThreadStatusWorkerEffort, WorkerFunding, ThreadStatusTodo, ThreadStatusDirector, ThreadStatusOutput, ThreadStatusEvent, ChatWork, ChatWorkThread,
130
+ ThreadStatusWaitingOn, ThreadStatusAssignee, ThreadStatusTodoActivity, ChatTodoPatch, ChatTodoComment,
131
+ MeStatus, MeStatusCounts,
118
132
  } from "./types/api-projects.js";
119
133
  export type {
120
134
  RegisterResult, RegisteredRuntime, RuntimeSourceInput, TemplateSourceRef, RegisterWorkflowInput,
@@ -124,7 +138,7 @@ export type {
124
138
  PublicFileLinkState,
125
139
  FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
126
140
  ScheduleRow, CreateScheduleInput,
127
- SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultCatalogEntry,
141
+ SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultKeyShape, VaultCatalogEntry, RequestedVaultEntry, VaultCardHint,
128
142
  CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage,
129
143
  UsageRollupRow, UsageResponse,
130
144
  DriveRepoLink, CreateDriveRepoLinkInput, CreateNativeRepoInput, CreateNativeRepoResult,
@@ -157,6 +171,16 @@ function detectAmbientParentRunId(): string | null {
157
171
  * URL-encoded as defense-in-depth: today's server-side patterns
158
172
  * (`^[a-z][a-z0-9-]*$`) can't produce reserved characters, but a future
159
173
  * relaxation would otherwise quietly become an injection vector. */
174
+ /** The query string of one page of a scope listing (`cursor`: the previous
175
+ * page's `nextCursor`; `limit`: 1..200). Empty for the first page at the
176
+ * server's size. */
177
+ function listingPageQuery(page: ListingPage = {}): string {
178
+ const q = new URLSearchParams();
179
+ if (page.cursor) q.set("cursor", page.cursor);
180
+ if (page.limit !== undefined) q.set("limit", String(page.limit));
181
+ return q.toString() ? `?${q}` : "";
182
+ }
183
+
160
184
  function templatePath(factorySlug: string, ...rest: string[]): string {
161
185
  const tail = rest.length > 0
162
186
  ? "/" + rest.map(encodeURIComponent).join("/")
@@ -660,6 +684,38 @@ export class AgentComposeClient {
660
684
  return body.workflows;
661
685
  }
662
686
 
687
+ /** A space's workflow activity: one stack per workflow of the space's own
688
+ * that has run there in the last 90 days — or, with `q`, that has a run
689
+ * matching the search, however old — the most recently run first, each
690
+ * with its latest (matching) run and what started it. The platform's
691
+ * default workflows (dynamic-task, repo-ingest, …) are left out, the
692
+ * search included. Paged by `cursor`. */
693
+ async listWorkflowActivity(opts: ListWorkflowActivityOptions = {}): Promise<WorkflowActivityPage> {
694
+ const q = new URLSearchParams();
695
+ if (opts.limit !== undefined) q.set("limit", String(opts.limit));
696
+ if (opts.cursor) q.set("cursor", opts.cursor);
697
+ if (opts.q) q.set("q", opts.q);
698
+ const slug = encodeURIComponent(opts.factorySlug ?? "default");
699
+ const body = await this.fetch<{ workflows: WorkflowActivityHead[]; has_more: boolean; next_cursor: string | null }>(
700
+ `/api/v1/factories/${slug}/workflow-activity${q.toString() ? `?${q}` : ""}`);
701
+ return { workflows: body.workflows, hasMore: body.has_more, nextCursor: body.next_cursor };
702
+ }
703
+
704
+ /** One workflow's runs in a space, newest first, by its exact registered
705
+ * name (`listRuns`' `workflow` is a substring match): the last 90 days,
706
+ * or with `q` every run matching the search; a platform default's name
707
+ * answers with no runs. Paged by `cursor`. */
708
+ async listWorkflowRuns(opts: ListWorkflowRunsOptions): Promise<WorkflowRunsPage> {
709
+ const q = new URLSearchParams({ workflow: opts.workflow });
710
+ if (opts.limit !== undefined) q.set("limit", String(opts.limit));
711
+ if (opts.cursor) q.set("cursor", opts.cursor);
712
+ if (opts.q) q.set("q", opts.q);
713
+ const slug = encodeURIComponent(opts.factorySlug ?? "default");
714
+ const body = await this.fetch<{ runs: WorkflowActivityRun[]; has_more: boolean; next_cursor: string | null }>(
715
+ `/api/v1/factories/${slug}/workflow-activity/runs?${q}`);
716
+ return { runs: body.runs, hasMore: body.has_more, nextCursor: body.next_cursor };
717
+ }
718
+
663
719
  /** Ordered lifecycle timeline for one run. */
664
720
  async getRunTimeline(runId: string, opts?: { limit?: number; offset?: number }): Promise<TimelineEvent[]> {
665
721
  const q = new URLSearchParams();
@@ -867,7 +923,7 @@ export class AgentComposeClient {
867
923
  // ── User drive mounts ──────────────────────────────────────────────────────
868
924
  // `agentc files mount` on a human's own machine: the server forks a user
869
925
  // branch, backs it with a mount session (the review surface), and mints a
870
- // user-principal gateway token. Human-held credentials only (sign-in
926
+ // user-principal gateway token. Own-device credentials only (sign-in
871
927
  // cookie or a device-flow bridge key) — sandbox session keys are refused.
872
928
 
873
929
  /** Create a local drive mount (or re-mint an existing one's token by
@@ -922,8 +978,18 @@ export class AgentComposeClient {
922
978
  getChannelIvy(id: string): Promise<ChannelIvyState> {
923
979
  return this.fetch(`/api/v1/conversations/${encodeURIComponent(id)}/ivy`);
924
980
  }
925
- listProjectIvyConnections(id: string): Promise<{ connections: ProjectIvyConnection[]; canManage: boolean }> {
926
- return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/ivy-connections`);
981
+ /** A project's GitHub grants: its own, and the workspace's it inherits,
982
+ * one page at a time (pass the previous page's `nextCursor`). A
983
+ * project's access is its members' (and the workspace admins'): a
984
+ * member of the workspace who can see a public project without being in
985
+ * it gets a 403 `project_member_required`. */
986
+ listProjectIvyConnections(id: string, page?: ListingPage): Promise<IvyConnectionsView> {
987
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/ivy-connections${listingPageQuery(page)}`);
988
+ }
989
+ /** A project's Drive shares: its own, and the workspace's it inherits,
990
+ * one page at a time. Members only, as listProjectIvyConnections. */
991
+ listProjectDriveShares(id: string, page?: ListingPage): Promise<DriveSharesView> {
992
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/drive-shares${listingPageQuery(page)}`);
927
993
  }
928
994
 
929
995
  /** List conversations the caller can access, newest-activity first.
@@ -1053,25 +1119,6 @@ export class AgentComposeClient {
1053
1119
  );
1054
1120
  }
1055
1121
 
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
1122
  /** Message ANOTHER session's conversation AS the calling session
1076
1123
  * (`agentc session message @alias`). Session toolbelt credential ONLY —
1077
1124
  * the sender resolves from the key. `target` is an @alias (team-scoped)
@@ -1178,6 +1225,32 @@ export class AgentComposeClient {
1178
1225
  );
1179
1226
  }
1180
1227
 
1228
+ // ── Session waits (owner ruling 2026-09-23: no watch runs inside a session
1229
+ // sandbox). The session declares what it waits for and ends its turn; the
1230
+ // platform senses the arrival and wakes it. Own-session key posture. ─────
1231
+
1232
+ /** Declare one wait (`agentc wait`): `POST /sessions/:id/waits`. */
1233
+ createSessionWait(conversationId: string, input: CreateSessionWaitInput): Promise<SessionWaitCreated> {
1234
+ return this.fetch<SessionWaitCreated>(
1235
+ `/api/v1/sessions/${encodeURIComponent(conversationId)}/waits`,
1236
+ { method: "POST", body: input },
1237
+ );
1238
+ }
1239
+
1240
+ /** This session's waits, newest first; open only unless `all`. */
1241
+ listSessionWaits(conversationId: string, opts: { all?: boolean } = {}): Promise<SessionWaitList> {
1242
+ const q = opts.all ? "?all=1" : "";
1243
+ return this.fetch<SessionWaitList>(`/api/v1/sessions/${encodeURIComponent(conversationId)}/waits${q}`);
1244
+ }
1245
+
1246
+ /** Cancel an open wait (`agentc wait cancel`): open -> cancelled. */
1247
+ cancelSessionWait(conversationId: string, waitId: string): Promise<SessionWait> {
1248
+ return this.fetch<SessionWait>(
1249
+ `/api/v1/sessions/${encodeURIComponent(conversationId)}/waits/${encodeURIComponent(waitId)}`,
1250
+ { method: "DELETE" },
1251
+ );
1252
+ }
1253
+
1181
1254
  /** Request ONE machine size up for a cloud session (task #110 — the
1182
1255
  * auto-resize policy's agent door). The platform arbitrates: within the
1183
1256
  * team's daily cap the upsize is auto-granted (and executes the moment
@@ -1277,17 +1350,30 @@ export class AgentComposeClient {
1277
1350
  return new Uint8Array(buf);
1278
1351
  }
1279
1352
 
1353
+ /** Where ONE path's current version lives for a session
1354
+ * (`GET /conversations/:id/changes/file`): `main` when the main drive
1355
+ * holds the session branch's version (landed, or never changed on the
1356
+ * branch), else `branch` with `why` naming what keeps it off main. The
1357
+ * answer a `file:<path>?session=<id>` link resolves when it is opened.
1358
+ * 404 = missing path or no access (uniform, no existence oracle). */
1359
+ getSessionFilePlane(conversationId: string, path: string): Promise<SessionFilePlane> {
1360
+ const q = new URLSearchParams({ path });
1361
+ return this.fetch<SessionFilePlane>(
1362
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/file?${q}`,
1363
+ );
1364
+ }
1365
+
1280
1366
  /** Publish a diff review's WHOLE document — prose notes plus structured
1281
1367
  * suggestions, FULL REPLACE on every call (`POST
1282
1368
  * /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`).
1369
+ * ONLY with the toolbelt credential of a review session spawned for this
1370
+ * diff, which replaces its own document — any other key (another
1371
+ * session, a human cookie, a plain API key) refuses 403.
1372
+ * `conversationId` is the REVIEWED session's conversation (in a review
1373
+ * sandbox: `AGENT_COMPOSE_REVIEW_OF_CONVERSATION_ID`).
1287
1374
  *
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
1375
+ * Failures: 403 `session_credential_required` | `not_review_session`;
1376
+ * 400 `suggestion_path_not_in_change_set` (naming the offenders); 409
1291
1377
  * `review_disabled`. */
1292
1378
  publishReviewNotes(
1293
1379
  conversationId: string,
@@ -1323,22 +1409,6 @@ export class AgentComposeClient {
1323
1409
  );
1324
1410
  }
1325
1411
 
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
1412
  /** Mint a short-lived native-git credential for a linked repo's MIRROR
1343
1413
  * namespace (`POST /:slug/repo-links/:id/git-credential`, git-door
1344
1414
  * slice 4). mode "fetch" (default) reads the mirror — gated on link
@@ -1485,8 +1555,10 @@ export class AgentComposeClient {
1485
1555
  return body.members;
1486
1556
  }
1487
1557
 
1488
- /** Remove a member (conversation `owner` or team admin only).
1489
- * Throws 400 `last_owner` when the target is the roster's last owner. */
1558
+ /** Remove a member (conversation `owner` or team admin), or leave the
1559
+ * chat yourself: `userId` "me" (or your own id) is the self-leave door.
1560
+ * Throws 400 `last_owner` when the target is the roster's last owner and
1561
+ * 400 `creator_cannot_leave` when the chat's creator tries to leave it. */
1490
1562
  async removeConversationMember(conversationId: string, userId: string): Promise<ConversationMember[]> {
1491
1563
  const body = await this.fetch<{ members: ConversationMember[] }>(
1492
1564
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/members/${encodeURIComponent(userId)}`,
@@ -1838,9 +1910,16 @@ export class AgentComposeClient {
1838
1910
 
1839
1911
  /** Rename a project (requires `write` role). */
1840
1912
  async renameProject(id: string, name: string): Promise<Project> {
1913
+ return this.updateProject(id, { name });
1914
+ }
1915
+
1916
+ /** Change a project's name, tier, description or dates (one PATCH; see
1917
+ * ProjectUpdate for who may set what). 400 `date_order` when the target
1918
+ * date would come before the start date. */
1919
+ async updateProject(id: string, patch: ProjectUpdate): Promise<Project> {
1841
1920
  const body = await this.fetch<{ project: Project }>(
1842
1921
  `/api/v1/projects/${encodeURIComponent(id)}`,
1843
- { method: "PATCH", body: { name } },
1922
+ { method: "PATCH", body: patch },
1844
1923
  );
1845
1924
  return body.project;
1846
1925
  }
@@ -1874,7 +1953,11 @@ export class AgentComposeClient {
1874
1953
  return body.members;
1875
1954
  }
1876
1955
 
1877
- /** Remove a member — project owner/team admin, or self-leave. Throws 400
1956
+ /** Remove a member — project owner/team admin, or self-leave (`me`).
1957
+ * Leaving a project takes its chats with it: the person loses their row
1958
+ * in every chat filed in the project, public and private, except a chat
1959
+ * they reach through another project they remain in, the home room, a
1960
+ * chat they created, and a chat they are the last owner of. Throws 400
1878
1961
  * `last_owner` when the target is the roster's last owner. */
1879
1962
  async removeProjectMember(id: string, userId: string): Promise<ProjectMember[]> {
1880
1963
  const body = await this.fetch<{ members: ProjectMember[] }>(
@@ -1884,6 +1967,14 @@ export class AgentComposeClient {
1884
1967
  return body.members;
1885
1968
  }
1886
1969
 
1970
+ /** What leaving the project would take from the caller: the chats they
1971
+ * lose with it, and the project's shared files and folders. */
1972
+ async leaveProjectPreview(id: string): Promise<{ chats: number; files: number }> {
1973
+ return this.fetch<{ chats: number; files: number }>(
1974
+ `/api/v1/projects/${encodeURIComponent(id)}/leave-preview`,
1975
+ );
1976
+ }
1977
+
1887
1978
  /** Change a member's role (project owner or team admin). Throws 400
1888
1979
  * `last_owner` when demoting the roster's last owner. */
1889
1980
  async setProjectMemberRole(
@@ -1898,15 +1989,56 @@ export class AgentComposeClient {
1898
1989
  return body.members;
1899
1990
  }
1900
1991
 
1901
- /** List a project's objects (member only), newest first. Cursor-paginated. */
1902
- listProjectObjects(id: string, opts?: { cursor?: string }): Promise<ProjectObjectsPage> {
1992
+ /** List a project's objects (member only), newest first. Cursor-paginated.
1993
+ * `type` narrows to one object kind at the server, under the same
1994
+ * cursor — `"conversation"` is a project's chats alone, however many
1995
+ * files it holds. */
1996
+ listProjectObjects(id: string, opts?: { cursor?: string; type?: ProjectObject["type"] }): Promise<ProjectObjectsPage> {
1903
1997
  const q = new URLSearchParams();
1904
1998
  if (opts?.cursor) q.set("cursor", opts.cursor);
1999
+ if (opts?.type) q.set("type", opts.type);
1905
2000
  return this.fetch<ProjectObjectsPage>(
1906
2001
  `/api/v1/projects/${encodeURIComponent(id)}/objects${q.toString() ? `?${q}` : ""}`,
1907
2002
  );
1908
2003
  }
1909
2004
 
2005
+ /** How many objects of each kind the project holds for the caller
2006
+ * (member only): the whole project's counts, where a page of
2007
+ * `listProjectObjects` holds at most fifty. */
2008
+ getProjectObjectCounts(id: string): Promise<ProjectObjectCounts> {
2009
+ return this.fetch<ProjectObjectCounts>(`/api/v1/projects/${encodeURIComponent(id)}/objects/counts`);
2010
+ }
2011
+
2012
+ /** The project status view (member only): its outcome threads with their
2013
+ * plans, workers, people and next dates, the project's progress, finish
2014
+ * line and next check-in, and what moved lately. `threads` is
2015
+ * cursor-paginated; `recentlyClosed` rides the first page. */
2016
+ getProjectStatus(id: string, opts?: { cursor?: string; limit?: number }): Promise<ProjectStatus> {
2017
+ const q = new URLSearchParams();
2018
+ if (opts?.cursor) q.set("cursor", opts.cursor);
2019
+ if (opts?.limit) q.set("limit", String(opts.limit));
2020
+ return this.fetch<ProjectStatus>(
2021
+ `/api/v1/projects/${encodeURIComponent(id)}/status${q.toString() ? `?${q}` : ""}`,
2022
+ );
2023
+ }
2024
+
2025
+ /** One thread's status for the inline expansion of a status row: its
2026
+ * summary, workers by state, to-dos and last events. 404 for a thread
2027
+ * the caller cannot read. */
2028
+ getThreadStatus(threadId: string): Promise<ThreadStatus> {
2029
+ return this.fetch<ThreadStatus>(`/api/v1/threads/${encodeURIComponent(threadId)}/status`);
2030
+ }
2031
+
2032
+ /** The caller's personal status in the current workspace: the threads
2033
+ * relevant to them (posted, mentioned, asked, a to-do, or in the chat),
2034
+ * grouped client-side by project. `threads` is cursor-paginated. */
2035
+ getMeStatus(opts?: { cursor?: string; limit?: number }): Promise<MeStatus> {
2036
+ const q = new URLSearchParams();
2037
+ if (opts?.cursor) q.set("cursor", opts.cursor);
2038
+ if (opts?.limit) q.set("limit", String(opts.limit));
2039
+ return this.fetch<MeStatus>(`/api/v1/me/status${q.toString() ? `?${q}` : ""}`);
2040
+ }
2041
+
1910
2042
  /** DRY-RUN preview of a session add: the files that WOULD be shared (from
1911
2043
  * the caller's owned scopes) + any owned-but-skipped entries (paths
1912
2044
  * concealed from the caller are omitted). Powers the consent dialog. Same
@@ -2083,9 +2215,10 @@ export class AgentComposeClient {
2083
2215
  { method: "POST", body: input });
2084
2216
  }
2085
2217
 
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. */
2218
+ /** The caller's own connector grants and the workspace's GitHub
2219
+ * installations (metadata only — never tokens; a teammate's other
2220
+ * accounts never come back). Used by `agentc repos link` to default
2221
+ * `--grant` when the team holds exactly one active GitHub grant. */
2089
2222
  async listConnectorGrants(): Promise<ConnectorGrantSummary[]> {
2090
2223
  const body = await this.fetch<{ data: ConnectorGrantSummary[] }>("/api/v1/connectors");
2091
2224
  return body.data;
@@ -2164,7 +2297,16 @@ export class AgentComposeClient {
2164
2297
  * writer. */
2165
2298
  requestSessionSecrets(
2166
2299
  conversationId: string, keys: string[],
2167
- opts?: { reason?: string; setLabel?: string; kind?: VaultRequestKind },
2300
+ opts?: {
2301
+ reason?: string; setLabel?: string; kind?: VaultRequestKind; shapes?: Record<string, VaultKeyShape>;
2302
+ savedEntry?: RequestedVaultEntry;
2303
+ /** A REPLACEMENT: the keys are held already and did not work; the card
2304
+ * asks for a new value of the same key. */
2305
+ replace?: boolean;
2306
+ /** A different credential of a kind the work already holds under
2307
+ * another name (the server refuses such an ask without it). */
2308
+ distinct?: boolean;
2309
+ },
2168
2310
  ): Promise<SessionSecretRequestCreated> {
2169
2311
  return this.fetch(
2170
2312
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests`,
@@ -2176,6 +2318,14 @@ export class AgentComposeClient {
2176
2318
  // WHAT KIND of credential — the vault page leads with the user's
2177
2319
  // matching STANDING entries (personal-vault reuse, 2026-08-31).
2178
2320
  ...(opts?.kind ? { kind: opts.kind } : {}),
2321
+ // WHICH standing entry — by label and/or a card's last four — so the
2322
+ // card and the vault page lead with that exact one.
2323
+ ...(opts?.savedEntry ? { savedEntry: opts.savedEntry } : {}),
2324
+ // Per-key DELIVERY (brokered onto a host, or injected) — required
2325
+ // for every key of an api_key ask.
2326
+ ...(opts?.shapes ? { shapes: opts.shapes } : {}),
2327
+ ...(opts?.replace ? { replace: true } : {}),
2328
+ ...(opts?.distinct ? { distinct: true } : {}),
2179
2329
  } });
2180
2330
  }
2181
2331
 
@@ -2183,8 +2333,11 @@ export class AgentComposeClient {
2183
2333
  * could grant to this session — labels, kinds, field NAMES, last-used.
2184
2334
  * Values are write-only and never on this wire. Query it BEFORE asking
2185
2335
  * 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 }> {
2336
+ * ask naming it, never a blank re-entry form. For the session's own key
2337
+ * it also carries `reaches`: what the session already reaches with no
2338
+ * credential asked for (`agentc access`), so a need met there is never
2339
+ * a vault ask. */
2340
+ getSessionVaultCatalog(conversationId: string): Promise<{ entries: VaultCatalogEntry[]; note?: string; reaches?: string }> {
2188
2341
  return this.fetch(
2189
2342
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/vault-catalog`);
2190
2343
  }
@@ -2192,7 +2345,7 @@ export class AgentComposeClient {
2192
2345
  /** Poll a vault link's status (`pending` → `fulfilled`; `expired` when the
2193
2346
  * clock ran out; `cancelled` when the human denied it or the requester
2194
2347
  * withdrew it — attribution rides the answer). The in-sandbox
2195
- * `agentc secrets request --wait` loop. */
2348
+ * `agentc secrets session request --wait` loop. */
2196
2349
  getSessionSecretRequest(conversationId: string, requestId: string): Promise<SessionSecretRequestStatus> {
2197
2350
  return this.fetch(
2198
2351
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests/${encodeURIComponent(requestId)}`);
@@ -2224,6 +2377,8 @@ export class AgentComposeClient {
2224
2377
  /** Create a new API key on the caller's team. The plaintext `key` is
2225
2378
  * returned once — it cannot be retrieved later.
2226
2379
  *
2380
+ * `scopes` omitted mints `read` alone; every scope asked for must be one
2381
+ * the caller holds (a key carries no more than whoever mints it).
2227
2382
  * When `factorySlug` is set, the new key is restricted to that factory.
2228
2383
  * Factory-scoped keys can only mint other keys bound to the same factory. */
2229
2384
  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