@agent-compose/sdk 0.8.3 → 0.8.5

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 (44) hide show
  1. package/dist/agent/agent-context.d.ts +9 -1
  2. package/dist/agent/agent-loop.d.ts +14 -1
  3. package/dist/client.d.ts +171 -33
  4. package/dist/directives.d.ts +14 -0
  5. package/dist/generated/verb-synopsis.d.ts +34 -0
  6. package/dist/index.d.ts +7 -5
  7. package/dist/index.js +1043 -41
  8. package/dist/runtimes/_cli-agent.d.ts +118 -0
  9. package/dist/runtimes/claude-code.d.ts +31 -1
  10. package/dist/runtimes/openai-desktop.d.ts +50 -0
  11. package/dist/runtimes/openai-desktop.js +1065 -59
  12. package/dist/runtimes/openai-desktop.test.d.ts +20 -0
  13. package/dist/runtimes/tool-pulse.test.d.ts +17 -0
  14. package/dist/sandbox/devbox.d.ts +5 -5
  15. package/dist/sandbox/registry.d.ts +12 -0
  16. package/dist/sandbox/sizes.d.ts +11 -5
  17. package/dist/sandbox.d.ts +2 -2
  18. package/dist/step-invocation/types.d.ts +1 -1
  19. package/dist/types/api-conversations.d.ts +85 -12
  20. package/dist/types/api-factory.d.ts +111 -1
  21. package/dist/types/conversation-stream.d.ts +22 -1
  22. package/dist/types/protocol.d.ts +130 -1
  23. package/dist/types/runtime.d.ts +71 -0
  24. package/package.json +1 -1
  25. package/src/agent/agent-context.ts +43 -9
  26. package/src/agent/agent-loop.ts +14 -3
  27. package/src/agent/desktop-open.ts +13 -1
  28. package/src/client.ts +256 -38
  29. package/src/directives.ts +21 -1
  30. package/src/generated/verb-synopsis.ts +544 -0
  31. package/src/index.ts +20 -5
  32. package/src/runtimes/_cli-agent.ts +333 -22
  33. package/src/runtimes/claude-code.ts +260 -14
  34. package/src/runtimes/openai-desktop.ts +82 -19
  35. package/src/sandbox/devbox.ts +5 -5
  36. package/src/sandbox/providers/e2b.ts +89 -17
  37. package/src/sandbox/registry.ts +19 -1
  38. package/src/sandbox/sizes.ts +11 -5
  39. package/src/sandbox.ts +2 -1
  40. package/src/types/api-conversations.ts +65 -13
  41. package/src/types/api-factory.ts +121 -1
  42. package/src/types/conversation-stream.ts +24 -1
  43. package/src/types/protocol.ts +127 -1
  44. package/src/types/runtime.ts +63 -0
package/src/client.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { ConversationRow, CreateChatInput, ChannelIvyState, ProjectIvyConnection } from "./types/api-conversations.js";
1
2
  /**
2
3
  * AgentComposeClient — HTTP client for the agent-compose server API.
3
4
  *
@@ -32,8 +33,9 @@ import type {
32
33
  ConversationsPage, SessionsPage, ConversationDetail, ConversationThread,
33
34
  CreateCloudSessionInput, CloudSessionCreated,
34
35
  SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
36
+ BackgroundWorkChildDecl, BackgroundWorkStatus, MachineUpsizeOutcome,
35
37
  SessionChangeSet, SessionMergeGated, SessionMergeReport, SessionDiscardReport,
36
- SessionChangePreflight, SessionRebaseReport, SessionAutoRebaseState,
38
+ SessionChangePreflight, SessionRebaseReport,
37
39
  ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential,
38
40
  SendConversationMessageInput, SendConversationMessageResult,
39
41
  ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
@@ -53,13 +55,14 @@ import type {
53
55
  import type {
54
56
  RegisterResult, RegisterWorkflowInput, TemplateRow, TemplateDetail, ListTemplatesOptions,
55
57
  SearchFactoryFilesOptions, FactoryFileSearchResult, FactoryFileWriteResult,
56
- FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage,
58
+ FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage, FactoryFileConflict, FactoryFileConflictList, ProjectSecretMeta, ProjectSecretList,
57
59
  FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
58
60
  ScheduleRow, CreateScheduleInput,
59
- SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus,
61
+ SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultCatalogEntry,
60
62
  CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage, UsageResponse,
61
- DriveRepoLink, CreateDriveRepoLinkInput,
63
+ DriveRepoLink, CreateDriveRepoLinkInput, CreateNativeRepoInput, CreateNativeRepoResult,
62
64
  DriveMountSession, CreateDriveMountSessionInput,
65
+ ConnectorGrantSummary,
63
66
  } from "./types/api-factory.js";
64
67
  import type {
65
68
  ComplianceSession, RequestComplianceSessionInput, ListComplianceSessionsOptions,
@@ -85,15 +88,17 @@ export type {
85
88
  } from "./types/api-runs.js";
86
89
  export type {
87
90
  TeamMember, Mention, CreateMentionsInput,
91
+ CreateChatInput, ChannelIvyState, ProjectIvyConnection,
88
92
  ConversationMessagePart, ConversationRow, ConversationMessageRow,
89
93
  ConversationsPage, SessionsPage, ConversationDetail,
90
94
  CreateCloudSessionInput, CloudSessionCreated, ConversationThread,
91
95
  SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
96
+ BackgroundWorkChildDecl, BackgroundWorkStatus, MachineUpsizeOutcome,
92
97
  SessionFileChange, SessionChangeSet, SessionChangeStats, SessionDiffReviewBound, ReviewSuggestion,
93
98
  ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential, SessionReviewDocument,
94
99
  ReviewSuggestionDecision, ReviewSuggestionDecisions,
95
100
  SessionMergeReportDetail, SessionMergeReport, SessionMergeGated, SessionDiscardReport,
96
- SessionPreflightFileRow, SessionChangePreflight, SessionRebaseReport, SessionAutoRebaseState,
101
+ SessionPreflightFileRow, SessionChangePreflight, SessionRebaseReport,
97
102
  ConversationPageContext, SendConversationMessageInput, ConversationTurnState,
98
103
  SendConversationMessageResult, UnnotifiedMention, ConversationPresenceSnapshot, AgentListRow, StreamConversationOptions,
99
104
  ChannelSessionStatus, ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
@@ -115,15 +120,16 @@ export type {
115
120
  RegisterResult, RegisteredRuntime, RuntimeSourceInput, TemplateSourceRef, RegisterWorkflowInput,
116
121
  TemplateRow, TemplateDetail, ListTemplatesOptions,
117
122
  FactoryFileSearchRow, FactoryFolderSearchRow, SearchFactoryFilesOptions, FactoryFileSearchResult, FactoryFileWriteResult,
118
- FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage,
123
+ FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage, FactoryFileConflict, FactoryFileConflictList, ProjectSecretMeta, ProjectSecretList,
119
124
  PublicFileLinkState,
120
125
  FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
121
126
  ScheduleRow, CreateScheduleInput,
122
- SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus,
127
+ SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus, SessionSecretRequestSummary, VaultRequestKind, VaultCatalogEntry,
123
128
  CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage,
124
129
  UsageRollupRow, UsageResponse,
125
- DriveRepoLink, CreateDriveRepoLinkInput,
130
+ DriveRepoLink, CreateDriveRepoLinkInput, CreateNativeRepoInput, CreateNativeRepoResult,
126
131
  DriveMountSession, CreateDriveMountSessionInput,
132
+ ConnectorGrantSummary,
127
133
  } from "./types/api-factory.js";
128
134
  export type {
129
135
  ComplianceScopeKind, ComplianceStatus, ComplianceSession, ComplianceAccess,
@@ -770,6 +776,55 @@ export class AgentComposeClient {
770
776
  };
771
777
  }
772
778
 
779
+ /** Bounded, cursor-paginated conflicts. A session filter is applied in the database. */
780
+ /** Project secrets (the group tier) — names + metadata, project members only. */
781
+ listProjectSecrets(projectId: string): Promise<ProjectSecretList> {
782
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(projectId)}/secrets`);
783
+ }
784
+
785
+ /** Upsert a project secret. Human credential required (a session toolbelt
786
+ * key is refused). Values are write-only — never echoed. */
787
+ upsertProjectSecret(projectId: string, secret: {
788
+ key: string; value: string;
789
+ delivery?: "injected" | "brokered"; brokerHost?: string; brokerHeaderName?: string; brokerHeaderScheme?: string;
790
+ }): Promise<{ key: string; delivery: "injected" | "brokered" }> {
791
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(projectId)}/secrets`, {
792
+ method: "POST", body: JSON.stringify(secret),
793
+ });
794
+ }
795
+
796
+ deleteProjectSecret(projectId: string, key: string): Promise<void> {
797
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(projectId)}/secrets/${encodeURIComponent(key)}`, { method: "DELETE" });
798
+ }
799
+
800
+ listFactoryFileConflicts(factorySlug: string, opts: { limit?: number; cursor?: string; conversationId?: string } = {}): Promise<FactoryFileConflictList> {
801
+ const q = new URLSearchParams();
802
+ if (opts.limit !== undefined) q.set("limit", String(opts.limit));
803
+ if (opts.cursor) q.set("cursor", opts.cursor);
804
+ if (opts.conversationId) q.set("conversationId", opts.conversationId);
805
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/conflicts?${q}`);
806
+ }
807
+
808
+ async getFactoryFileConflictContent(factorySlug: string, id: string): Promise<Uint8Array> {
809
+ const bytes = await this.fetch<ArrayBuffer, "arrayBuffer">(
810
+ `/api/v1/factories/${encodeURIComponent(factorySlug)}/files/conflicts/${encodeURIComponent(id)}/content`,
811
+ { responseType: "arrayBuffer" },
812
+ );
813
+ return new Uint8Array(bytes);
814
+ }
815
+
816
+ /** Records a decision about the exact inspected versions. Stale arms refuse. */
817
+ resolveFactoryFileConflict(factorySlug: string, id: string, decision: {
818
+ status: "resolved" | "dismissed";
819
+ settle: "branch" | "main";
820
+ expectedOursHash: string | null;
821
+ expectedTheirsHash: string | null;
822
+ }): Promise<FactoryFileConflict & { settled: "branch" | "main" | "row" }> {
823
+ return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/conflicts/${encodeURIComponent(id)}/resolve`, {
824
+ method: "POST", body: JSON.stringify(decision),
825
+ });
826
+ }
827
+
773
828
  /** Read one file's current content (or a specific revision) as RAW BYTES —
774
829
  * byte-exact for any content type. This is the wire truth; `getFactoryFile`
775
830
  * is a UTF-8 decode over it. Binaries (images, archives) MUST come through
@@ -860,6 +915,17 @@ export class AgentComposeClient {
860
915
  // access. Key callers see what their sender scope allows; the server is the
861
916
  // sole authority — these are thin typed wrappers.
862
917
 
918
+ /** Create a shared chat, including its own Ivy by default. */
919
+ createChat(input: CreateChatInput): Promise<ConversationRow> {
920
+ return this.fetch("/api/v1/conversations", { method: "POST", body: JSON.stringify(input), headers: { "content-type": "application/json" } });
921
+ }
922
+ getChannelIvy(id: string): Promise<ChannelIvyState> {
923
+ return this.fetch(`/api/v1/conversations/${encodeURIComponent(id)}/ivy`);
924
+ }
925
+ listProjectIvyConnections(id: string): Promise<{ connections: ProjectIvyConnection[]; canManage: boolean }> {
926
+ return this.fetch(`/api/v1/projects/${encodeURIComponent(id)}/ivy-connections`);
927
+ }
928
+
863
929
  /** List conversations the caller can access, newest-activity first.
864
930
  * Cursor-paginated: pass the previous page's `next_cursor`. */
865
931
  listConversations(opts?: { cursor?: string }): Promise<ConversationsPage> {
@@ -1095,19 +1161,62 @@ export class AgentComposeClient {
1095
1161
  * background-work busy lease: while it is live, the between-turns
1096
1162
  * park/suspend leaves the session's VM running so work the turn left
1097
1163
  * behind keeps executing. The lease lapses on its own — re-hold to
1098
- * extend past `minutes` (server-capped). Write-tier. */
1099
- holdBackgroundWork(conversationId: string, input?: { minutes?: number }): Promise<BackgroundWorkHeld> {
1164
+ * extend past `minutes` (server-capped). `children` descriptors
1165
+ * ({pid, journalPath, label}) register the DETACHED processes the hold
1166
+ * vouches for, so the platform can verify and reattach them after a
1167
+ * park (task #63). Write-tier. */
1168
+ holdBackgroundWork(
1169
+ conversationId: string,
1170
+ input?: { minutes?: number; children?: BackgroundWorkChildDecl[] },
1171
+ ): Promise<BackgroundWorkHeld> {
1100
1172
  return this.fetch<BackgroundWorkHeld>(
1101
1173
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`,
1102
- { method: "POST", body: { ...(input?.minutes !== undefined ? { minutes: input.minutes } : {}) } },
1174
+ { method: "POST", body: {
1175
+ ...(input?.minutes !== undefined ? { minutes: input.minutes } : {}),
1176
+ ...(input?.children !== undefined ? { children: input.children } : {}),
1177
+ } },
1178
+ );
1179
+ }
1180
+
1181
+ /** Request ONE machine size up for a cloud session (task #110 — the
1182
+ * auto-resize policy's agent door). The platform arbitrates: within the
1183
+ * team's daily cap the upsize is auto-granted (and executes the moment
1184
+ * no live turn / background work holds the machine — home essentials
1185
+ * carry over); past the cap it becomes an owner approval card.
1186
+ * Deliberately not `resize`: the in-sandbox toolbelt key may call THIS,
1187
+ * because the kill only ever happens under platform policy or human
1188
+ * approval. Refusals (memory ceiling, template-pinned, ended) arrive as
1189
+ * HTTP 409s carrying `code`. Write-tier. */
1190
+ requestMachineUpsize(
1191
+ conversationId: string, input?: { reason?: string },
1192
+ ): Promise<MachineUpsizeOutcome> {
1193
+ return this.fetch<MachineUpsizeOutcome>(
1194
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/machine/request-upsize`,
1195
+ { method: "POST", body: input?.reason ? { reason: input.reason } : {} },
1196
+ );
1197
+ }
1198
+
1199
+ /** The session's background-work status: the lease deadline, the declared
1200
+ * children, and the owed-report marker a mid-work park leaves behind.
1201
+ * Read-tier. */
1202
+ getBackgroundWork(conversationId: string): Promise<BackgroundWorkStatus> {
1203
+ return this.fetch<BackgroundWorkStatus>(
1204
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`,
1103
1205
  );
1104
1206
  }
1105
1207
 
1106
1208
  /** Release the session's background-work lease (the work finished).
1107
- * Idempotent — `released` is false when no lease was held. */
1108
- async releaseBackgroundWork(conversationId: string): Promise<boolean> {
1209
+ * Idempotent — `released` is false when no lease was held. `pid` scopes
1210
+ * the release to ONE declared child (what a `--while-pid` holder passes
1211
+ * when its watched process dies): the child is pruned from the declared
1212
+ * registry, and the lease clears only when it was the last — a sibling's
1213
+ * death never erases the other holders' declarations. */
1214
+ async releaseBackgroundWork(
1215
+ conversationId: string, opts?: { pid?: number },
1216
+ ): Promise<boolean> {
1217
+ const pidQuery = opts?.pid !== undefined ? `?pid=${encodeURIComponent(opts.pid)}` : "";
1109
1218
  const body = await this.fetch<{ released: boolean }>(
1110
- `/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`,
1219
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work${pidQuery}`,
1111
1220
  { method: "DELETE" },
1112
1221
  );
1113
1222
  return body.released;
@@ -1214,6 +1323,59 @@ export class AgentComposeClient {
1214
1323
  );
1215
1324
  }
1216
1325
 
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
+ /** Mint a short-lived native-git credential for a linked repo's MIRROR
1343
+ * namespace (`POST /:slug/repo-links/:id/git-credential`, git-door
1344
+ * slice 4). mode "fetch" (default) reads the mirror — gated on link
1345
+ * visibility; mode "mirror" pushes real GitHub history through the
1346
+ * closure-verified door — gated on the repo_link WRITE capability.
1347
+ * 409 `git_remote_unavailable` when no git plane serves the factory. */
1348
+ getRepoLinkGitCredential(linkId: string, opts?: { mode?: "fetch" | "mirror" | "github" | "native"; factorySlug?: string }):
1349
+ Promise<{
1350
+ url: string; username: string; password: string; repoUrl: string;
1351
+ /** Absent in `github` mode (the credential targets github.com, not the platform git plane). */
1352
+ namespace?: string; expiresAt: string; mode: string;
1353
+ /** `github` mode only: the host the credential is for. */
1354
+ host?: string;
1355
+ /** `github` mode only: the token's ceiling — "write" (contents:write,
1356
+ * push-capable) for viewers with write capability on the link,
1357
+ * "read" (clone/fetch only) otherwise. */
1358
+ access?: "read" | "write";
1359
+ /** `github` mode only: the GRANTED permission map GitHub echoed at
1360
+ * mint (e.g. `{contents:"write", pulls:"write"}`). A capability
1361
+ * missing here (pulls, issues) means the App installation does not
1362
+ * carry it — the OWNER must widen the installation; retrying can
1363
+ * never help. */
1364
+ permissions?: Record<string, string>;
1365
+ /** `github` mode only: the INSTALLATION's recorded grant ledger
1366
+ * (`key:level` strings, probe-synced). A capability present here
1367
+ * but missing from `permissions` is a platform downscope; missing
1368
+ * from both layers, an App-installation gap only the owner can
1369
+ * widen. Empty = not yet recorded (reconnect or next probe). */
1370
+ installationScopes?: string[];
1371
+ }> {
1372
+ const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
1373
+ return this.fetch(
1374
+ `/api/v1/factories/${encodeURIComponent(slug)}/repo-links/${encodeURIComponent(linkId)}/git-credential`,
1375
+ { method: "POST", body: JSON.stringify({ mode: opts?.mode ?? "fetch" }), headers: { "content-type": "application/json" } },
1376
+ );
1377
+ }
1378
+
1217
1379
  /** Approve the session's proposed changes: 3-way merge its branch into
1218
1380
  * `main`, then continue the session on a fresh branch off post-merge
1219
1381
  * `main`. Write-tier + human caller. Pass `opts.branch` (the branch you
@@ -1224,9 +1386,14 @@ export class AgentComposeClient {
1224
1386
  * converged on) a kind='merge' approval routed to the session's
1225
1387
  * approvers. Discriminate on `object`.
1226
1388
  *
1227
- * Failures: 403 `role_read_only` (read-only member) or a plain 403 for
1228
- * session toolbelt keys (agents cannot self-approve); 409 `turn_active`
1229
- * (a turn is running — retry when idle) | `branch_changed`; 400
1389
+ * Failures: 403 `role_read_only` (read-only member); 403 `not_own_branch`
1390
+ * (a session toolbelt key merges ONLY its own bound session's branch —
1391
+ * the agent self-merge door, owner ruling 2026-08-31); 403 `merge_gated`
1392
+ * (a gated session's self-merge — a designated approver merges from the
1393
+ * Changes panel instead, and no card is minted); 409 `turn_active` (a
1394
+ * turn is running — retry when idle; a session's OWN self-merge is
1395
+ * exempt) | `branch_changed` | `branch_claimed` (a teammate session
1396
+ * holds the branch claim — merge after it releases); 400
1230
1397
  * `no_branch` (session predates branching); 502 `quiesce_failed`
1231
1398
  * (nothing changed — safe to retry) | `merge_failed` (branch retained,
1232
1399
  * named in the body) | `rebranch_failed` (merge landed; retry safe). */
@@ -1290,24 +1457,6 @@ export class AgentComposeClient {
1290
1457
  );
1291
1458
  }
1292
1459
 
1293
- /** Arm/disarm the OPT-IN main-advance auto-rebase reflex for a session
1294
- * (default OFF): when armed and the drive's main moves, the platform
1295
- * preflights and — only on a provably clean fold (zero conflicts) —
1296
- * rebases the session's branch from main headlessly, posting a quiet
1297
- * receipt. Trade-off (why default OFF): the agent's view of files it
1298
- * did not touch can change mid-task. Human, write-tier act.
1299
- *
1300
- * Failures: 409 `review_session_scratch` | `plane_unsupported`; 403
1301
- * (needs a user-bound human caller with write access). */
1302
- setSessionAutoRebase(
1303
- conversationId: string, enabled: boolean,
1304
- ): Promise<SessionAutoRebaseState> {
1305
- return this.fetch<SessionAutoRebaseState>(
1306
- `/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/autorebase`,
1307
- { method: "POST", body: { enabled } },
1308
- );
1309
- }
1310
-
1311
1460
  // ── Conversation membership (ADR-0045) ────────────────────────────────────
1312
1461
  // Channel rosters carry roles (`owner > write > read`). Inviting is a
1313
1462
  // `write` action; removing and re-roling are `owner` (or team admin)
@@ -1497,6 +1646,13 @@ export class AgentComposeClient {
1497
1646
  };
1498
1647
  }
1499
1648
 
1649
+ /** The credential's user role on the active team; null for a creator-less
1650
+ * key or a user with no membership. */
1651
+ async getTeamRole(): Promise<string | null> {
1652
+ const body = await this.fetch<{ role: string | null }>("/api/v1/team/role");
1653
+ return body.role;
1654
+ }
1655
+
1500
1656
  /** List the human members of your team — the people you (or an agent) can
1501
1657
  * @-flag with `createMentions`. Each row's `userId` is what
1502
1658
  * `mentionedUserIds` expects. */
@@ -1914,6 +2070,27 @@ export class AgentComposeClient {
1914
2070
  return body.links;
1915
2071
  }
1916
2072
 
2073
+ /** Create a repo whose origin IS Agent Compose (`manage` scope) — no
2074
+ * GitHub behind it; clone/fetch/push ride the platform git door. 409
2075
+ * names the missing prerequisite (graph drive, git plane) or an
2076
+ * overlapping placement. */
2077
+ async createNativeRepo(
2078
+ input: CreateNativeRepoInput, opts?: { factorySlug?: string },
2079
+ ): Promise<CreateNativeRepoResult> {
2080
+ const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
2081
+ return this.fetch<CreateNativeRepoResult>(
2082
+ `/api/v1/factories/${encodeURIComponent(slug)}/repo-links/native`,
2083
+ { method: "POST", body: input });
2084
+ }
2085
+
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. */
2089
+ async listConnectorGrants(): Promise<ConnectorGrantSummary[]> {
2090
+ const body = await this.fetch<{ data: ConnectorGrantSummary[] }>("/api/v1/connectors");
2091
+ return body.data;
2092
+ }
2093
+
1917
2094
  /** Unlink (§6.3: the prefix's files and history stay on the drive). */
1918
2095
  deleteRepoLink(linkId: string, opts?: { factorySlug?: string }): Promise<void> {
1919
2096
  const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
@@ -1986,20 +2163,61 @@ export class AgentComposeClient {
1986
2163
  * toolbelt credential (the in-sandbox agent's door) or by a session
1987
2164
  * writer. */
1988
2165
  requestSessionSecrets(
1989
- conversationId: string, keys: string[], opts?: { reason?: string },
2166
+ conversationId: string, keys: string[],
2167
+ opts?: { reason?: string; setLabel?: string; kind?: VaultRequestKind },
1990
2168
  ): Promise<SessionSecretRequestCreated> {
1991
2169
  return this.fetch(
1992
2170
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests`,
1993
- { method: "POST", body: { keys, ...(opts?.reason ? { reason: opts.reason } : {}) } });
2171
+ { method: "POST", body: {
2172
+ keys,
2173
+ ...(opts?.reason ? { reason: opts.reason } : {}),
2174
+ // Suggested batch name — prefills the vault form's save-as-set label.
2175
+ ...(opts?.setLabel ? { setLabel: opts.setLabel } : {}),
2176
+ // WHAT KIND of credential — the vault page leads with the user's
2177
+ // matching STANDING entries (personal-vault reuse, 2026-08-31).
2178
+ ...(opts?.kind ? { kind: opts.kind } : {}),
2179
+ } });
2180
+ }
2181
+
2182
+ /** The session's STANDING-vault catalog (2026-08-31): entries the vault
2183
+ * could grant to this session — labels, kinds, field NAMES, last-used.
2184
+ * Values are write-only and never on this wire. Query it BEFORE asking
2185
+ * 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 }> {
2188
+ return this.fetch(
2189
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/vault-catalog`);
1994
2190
  }
1995
2191
 
1996
2192
  /** Poll a vault link's status (`pending` → `fulfilled`; `expired` when the
1997
- * clock ran out). The in-sandbox `agentc secrets request --wait` loop. */
2193
+ * clock ran out; `cancelled` when the human denied it or the requester
2194
+ * withdrew it — attribution rides the answer). The in-sandbox
2195
+ * `agentc secrets request --wait` loop. */
1998
2196
  getSessionSecretRequest(conversationId: string, requestId: string): Promise<SessionSecretRequestStatus> {
1999
2197
  return this.fetch(
2000
2198
  `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests/${encodeURIComponent(requestId)}`);
2001
2199
  }
2002
2200
 
2201
+ /** List a session's OPEN vault requests (pending + unexpired), newest
2202
+ * first — receipts only (keys, reason, clocks). The cancel verb's
2203
+ * `--all` enumeration. */
2204
+ async listSessionSecretRequests(conversationId: string): Promise<SessionSecretRequestSummary[]> {
2205
+ const body = await this.fetch<{ requests: SessionSecretRequestSummary[] }>(
2206
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests`);
2207
+ return body.requests;
2208
+ }
2209
+
2210
+ /** Cancel one vault request (task #111). The requesting agent withdraws
2211
+ * its own ask (session toolbelt key), or a session writer denies it.
2212
+ * 409 `request_not_pending` when it already settled. */
2213
+ cancelSessionSecretRequest(
2214
+ conversationId: string, requestId: string, opts?: { reason?: string },
2215
+ ): Promise<{ requestId: string; status: "cancelled" }> {
2216
+ return this.fetch(
2217
+ `/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests/${encodeURIComponent(requestId)}/cancel`,
2218
+ { method: "POST", body: { ...(opts?.reason ? { reason: opts.reason } : {}) } });
2219
+ }
2220
+
2003
2221
  // ── API keys ───────────────────────────────────────────────────────────────
2004
2222
  // Both endpoints require an admin-scoped key as the bearer token.
2005
2223
 
package/src/directives.ts CHANGED
@@ -61,7 +61,15 @@ export type CloudDirective =
61
61
  /** Interactive question card gated on a named approver — resolution and
62
62
  * the mentions ping are server-authoritative (`display_ask` shape). The
63
63
  * one write-bearing directive (see the module header). */
64
- | { kind: "ask"; prompt: string; approver: string; options?: Array<{ id: string; label: string }> };
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
68
+ * by itself: the link's viewer must still be a signed-in member with
69
+ * write access to THIS session (the server re-checks on every resolve).
70
+ * ONLY for structurally-human walls (a CAPTCHA, human-only verification)
71
+ * or when the user asked to do it themselves. */
72
+ | { kind: "takeover"; note?: string };
65
73
 
66
74
  /** `runId` values the run directives accept: a UUID, or the literal
67
75
  * `latest` (resolved server-side against the session's factory, optionally
@@ -85,6 +93,10 @@ export const ASK_DIRECTIVE_OPTION_LABEL_MAX = 60;
85
93
  export const ASK_APPROVER_MIN = 3;
86
94
  export const ASK_APPROVER_MAX = 320;
87
95
 
96
+ /** Takeover-note bound — one line naming what the human must do on the
97
+ * screen (mirrors the display-desktop caption cap). */
98
+ export const TAKEOVER_NOTE_MAX_CHARS = 200;
99
+
88
100
  function validDirectivePath(v: unknown): string | null {
89
101
  return typeof v === "string" && v.length >= 1 && v.length <= DIRECTIVE_PATH_MAX ? v : null;
90
102
  }
@@ -152,6 +164,14 @@ function validDirective(value: unknown): CloudDirective | null {
152
164
  }
153
165
  return { kind: "ask", prompt, approver, ...(options !== undefined ? { options } : {}) };
154
166
  }
167
+ if (d.kind === "takeover") {
168
+ const note = d.note;
169
+ if (note !== undefined
170
+ && (typeof note !== "string" || note.length < 1 || note.length > TAKEOVER_NOTE_MAX_CHARS)) {
171
+ return null;
172
+ }
173
+ return { kind: "takeover", ...(note !== undefined ? { note } : {}) };
174
+ }
155
175
  return null;
156
176
  }
157
177