@agent-compose/sdk 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/__tests__/perf-sampler.test.d.ts +10 -0
- package/dist/agent/agent-context.d.ts +1 -1
- package/dist/agent/agent-loop.d.ts +5 -1
- package/dist/agent/desktop-open.d.ts +184 -0
- package/dist/agent/perf-sampler.d.ts +99 -0
- package/dist/agent/services-manifest.d.ts +88 -0
- package/dist/agent/services-restore.d.ts +58 -0
- package/dist/client.d.ts +189 -15
- package/dist/display.d.ts +17 -0
- package/dist/index.d.ts +14 -5
- package/dist/index.js +1625 -120
- package/dist/runtimes/_cli-agent.d.ts +372 -2
- package/dist/runtimes/claude-code.d.ts +12 -0
- package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
- package/dist/runtimes/codex.d.ts +8 -0
- package/dist/runtimes/openai-desktop.js +1555 -120
- package/dist/runtimes/session-env.test.d.ts +14 -0
- package/dist/sandbox/sizes.d.ts +120 -30
- package/dist/sandbox.d.ts +1 -1
- package/dist/types/api-conversations.d.ts +476 -1
- package/dist/types/api-factory.d.ts +164 -7
- package/dist/types/api-runs.d.ts +23 -1
- package/dist/types/protocol.d.ts +32 -1
- package/dist/types/runtime.d.ts +120 -0
- package/dist/types/workflow-metadata.d.ts +6 -5
- package/package.json +1 -1
- package/src/agent/agent-context.ts +128 -28
- package/src/agent/agent-loop.ts +10 -3
- package/src/agent/desktop-open.ts +418 -0
- package/src/agent/perf-sampler.ts +202 -0
- package/src/agent/services-manifest.ts +356 -0
- package/src/agent/services-restore.ts +195 -0
- package/src/client.ts +384 -32
- package/src/display.ts +44 -1
- package/src/index.ts +74 -7
- package/src/runtimes/_cli-agent.ts +1160 -67
- package/src/runtimes/claude-code.ts +187 -12
- package/src/runtimes/codex.ts +65 -2
- package/src/sandbox/providers/e2b.ts +8 -4
- package/src/sandbox/providers/local.ts +16 -4
- package/src/sandbox/sizes.ts +127 -44
- package/src/sandbox.ts +8 -0
- package/src/types/api-conversations.ts +461 -2
- package/src/types/api-factory.ts +165 -7
- package/src/types/api-runs.ts +25 -1
- package/src/types/protocol.ts +30 -1
- package/src/types/runtime.ts +122 -0
- package/src/types/workflow-metadata.ts +6 -5
package/src/client.ts
CHANGED
|
@@ -23,7 +23,7 @@ import type {
|
|
|
23
23
|
RunStatus, ResumePauseOptions, ResumePauseResponse, AnswerSteerOptions,
|
|
24
24
|
RequestAgentPauseOptions, RequestAgentPauseResponse,
|
|
25
25
|
SendAgentMessageOptions, SendAgentMessageResponse,
|
|
26
|
-
RunDetail, RunListEntry, ListRunsOptions, TimelineEvent, RunFundingResponse, EventRow, ReportEventInput,
|
|
26
|
+
RunDetail, RunListEntry, ListRunsOptions, TimelineEvent, RunFundingResponse, RunStepUsageResponse, EventRow, ReportEventInput,
|
|
27
27
|
ListEventsOptions, ListEventsResult, RunArtifactRow, RunLogLine, ListRunLogsOptions,
|
|
28
28
|
CancelRunResponse, ListSnapshotsOptions, SnapshotListResponse, SnapshotListEntry, RunSnapshotEntry,
|
|
29
29
|
} from "./types/api-runs.js";
|
|
@@ -31,11 +31,15 @@ import type {
|
|
|
31
31
|
TeamMember, Mention, CreateMentionsInput,
|
|
32
32
|
ConversationsPage, SessionsPage, ConversationDetail, ConversationThread,
|
|
33
33
|
CreateCloudSessionInput, CloudSessionCreated,
|
|
34
|
-
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked,
|
|
35
|
-
SessionChangeSet, SessionMergeReport, SessionDiscardReport,
|
|
34
|
+
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
|
|
35
|
+
SessionChangeSet, SessionMergeGated, SessionMergeReport, SessionDiscardReport,
|
|
36
|
+
SessionChangePreflight, SessionRebaseReport, SessionAutoRebaseState,
|
|
37
|
+
ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential,
|
|
36
38
|
SendConversationMessageInput, SendConversationMessageResult,
|
|
37
39
|
ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
|
|
40
|
+
SessionDirectMessageSent, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
|
|
38
41
|
ConversationPresenceSnapshot, StreamConversationOptions, AgentListRow,
|
|
42
|
+
SessionPerfHistoryPage,
|
|
39
43
|
} from "./types/api-conversations.js";
|
|
40
44
|
import type {
|
|
41
45
|
ConversationMemberRole, ConversationMember, ArtifactScope,
|
|
@@ -49,10 +53,11 @@ import type {
|
|
|
49
53
|
import type {
|
|
50
54
|
RegisterResult, RegisterWorkflowInput, TemplateRow, TemplateDetail, ListTemplatesOptions,
|
|
51
55
|
SearchFactoryFilesOptions, FactoryFileSearchResult, FactoryFileWriteResult,
|
|
52
|
-
|
|
56
|
+
FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage,
|
|
57
|
+
FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
|
|
53
58
|
ScheduleRow, CreateScheduleInput,
|
|
54
|
-
SecretOptions, SetSecretResult, SecretListEntry,
|
|
55
|
-
CreateApiKeyInput, ApiKey, ApiKeyCreated, UsageResponse,
|
|
59
|
+
SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus,
|
|
60
|
+
CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage, UsageResponse,
|
|
56
61
|
DriveRepoLink, CreateDriveRepoLinkInput,
|
|
57
62
|
DriveMountSession, CreateDriveMountSessionInput,
|
|
58
63
|
} from "./types/api-factory.js";
|
|
@@ -73,6 +78,7 @@ export type {
|
|
|
73
78
|
SendAgentMessageOptions, SendAgentMessageResponse,
|
|
74
79
|
RunDetail, RunListEntry, ListRunsOptions, TimelineEvent,
|
|
75
80
|
FundingLane, RunFundingStamp, RunFundingUsageRow, RunFundingResponse,
|
|
81
|
+
RunStepUsage, RunStepUsageResponse,
|
|
76
82
|
EventSubjectType, EventRow, RunArtifactRow, ReportEventInput, ListEventsOptions, ListEventsResult,
|
|
77
83
|
RunLogLine, ListRunLogsOptions, CancelRunResponse,
|
|
78
84
|
ListSnapshotsOptions, SnapshotListEntry, SnapshotListResponse, RunSnapshotEntry,
|
|
@@ -82,12 +88,17 @@ export type {
|
|
|
82
88
|
ConversationMessagePart, ConversationRow, ConversationMessageRow,
|
|
83
89
|
ConversationsPage, SessionsPage, ConversationDetail,
|
|
84
90
|
CreateCloudSessionInput, CloudSessionCreated, ConversationThread,
|
|
85
|
-
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked,
|
|
86
|
-
SessionFileChange, SessionChangeSet,
|
|
87
|
-
|
|
91
|
+
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
|
|
92
|
+
SessionFileChange, SessionChangeSet, SessionChangeStats, SessionDiffReviewBound, ReviewSuggestion,
|
|
93
|
+
ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential, SessionReviewDocument,
|
|
94
|
+
ReviewSuggestionDecision, ReviewSuggestionDecisions,
|
|
95
|
+
SessionMergeReportDetail, SessionMergeReport, SessionMergeGated, SessionDiscardReport,
|
|
96
|
+
SessionPreflightFileRow, SessionChangePreflight, SessionRebaseReport, SessionAutoRebaseState,
|
|
88
97
|
ConversationPageContext, SendConversationMessageInput, ConversationTurnState,
|
|
89
|
-
SendConversationMessageResult, ConversationPresenceSnapshot, AgentListRow, StreamConversationOptions,
|
|
98
|
+
SendConversationMessageResult, UnnotifiedMention, ConversationPresenceSnapshot, AgentListRow, StreamConversationOptions,
|
|
90
99
|
ChannelSessionStatus, ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
|
|
100
|
+
SessionDirectMessageSent, BranchClaimInfo, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
|
|
101
|
+
SessionPerfBucket, SessionPerfHistoryPage,
|
|
91
102
|
} from "./types/api-conversations.js";
|
|
92
103
|
export type {
|
|
93
104
|
ConversationMemberRole, ConversationMember,
|
|
@@ -104,11 +115,12 @@ export type {
|
|
|
104
115
|
RegisterResult, RegisteredRuntime, RuntimeSourceInput, TemplateSourceRef, RegisterWorkflowInput,
|
|
105
116
|
TemplateRow, TemplateDetail, ListTemplatesOptions,
|
|
106
117
|
FactoryFileSearchRow, FactoryFolderSearchRow, SearchFactoryFilesOptions, FactoryFileSearchResult, FactoryFileWriteResult,
|
|
118
|
+
FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage,
|
|
107
119
|
PublicFileLinkState,
|
|
108
|
-
FactoryRow, CreateFactoryInput, UpdateFactoryInput,
|
|
120
|
+
FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
|
|
109
121
|
ScheduleRow, CreateScheduleInput,
|
|
110
|
-
SecretOptions, SetSecretResult, SecretListEntry,
|
|
111
|
-
CreateApiKeyInput, ApiKey, ApiKeyCreated,
|
|
122
|
+
SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus,
|
|
123
|
+
CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage,
|
|
112
124
|
UsageRollupRow, UsageResponse,
|
|
113
125
|
DriveRepoLink, CreateDriveRepoLinkInput,
|
|
114
126
|
DriveMountSession, CreateDriveMountSessionInput,
|
|
@@ -164,6 +176,17 @@ interface FactoryFileWriteWire {
|
|
|
164
176
|
created: boolean;
|
|
165
177
|
}
|
|
166
178
|
|
|
179
|
+
/** `GET /factories/:slug/files` — the rows are camelCase on the wire
|
|
180
|
+
* already; only the envelope is snake_case. `at` appears exactly when the
|
|
181
|
+
* request carried `?at=`. */
|
|
182
|
+
interface FactoryFileListWire {
|
|
183
|
+
object: "list";
|
|
184
|
+
data: FactoryFileListRow[];
|
|
185
|
+
has_more: boolean;
|
|
186
|
+
next_cursor: string | null;
|
|
187
|
+
at?: { head: string; committed_at: string | null };
|
|
188
|
+
}
|
|
189
|
+
|
|
167
190
|
export interface AgentComposeClientOptions {
|
|
168
191
|
/** Your team's API key — minted from the dashboard or `agentc keys create`.
|
|
169
192
|
* Required. Resolved from `process.env.AGENT_COMPOSE_API_KEY` when omitted. */
|
|
@@ -652,6 +675,16 @@ export class AgentComposeClient {
|
|
|
652
675
|
);
|
|
653
676
|
}
|
|
654
677
|
|
|
678
|
+
/** Attested platform tokens attributed to each of a run's steps, derived
|
|
679
|
+
* server-side from the spend ledger by step time window. Empty `steps` is
|
|
680
|
+
* an honest answer (legacy run, or funded outside the platform lane —
|
|
681
|
+
* that money is never metered). */
|
|
682
|
+
async getRunStepUsage(runId: string): Promise<RunStepUsageResponse> {
|
|
683
|
+
return this.fetch<RunStepUsageResponse>(
|
|
684
|
+
`/api/v1/workflows/${encodeURIComponent(runId)}/step-usage`,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
655
688
|
/** Report a durable event against a run. Events are late-binding facts
|
|
656
689
|
* like quality.accepted, defect.regression, or intervention.override. */
|
|
657
690
|
async reportEvent(runId: string, input: ReportEventInput): Promise<EventRow> {
|
|
@@ -744,7 +777,7 @@ export class AgentComposeClient {
|
|
|
744
777
|
* U+FFFD and the original bytes are unrecoverable). */
|
|
745
778
|
async getFactoryFileBytes(
|
|
746
779
|
path: string,
|
|
747
|
-
opts?: { factorySlug?: string; revision?: number; branch?: string },
|
|
780
|
+
opts?: { factorySlug?: string; revision?: number; branch?: string; at?: string },
|
|
748
781
|
): Promise<Uint8Array> {
|
|
749
782
|
const factorySlug = opts?.factorySlug
|
|
750
783
|
?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined)
|
|
@@ -755,6 +788,10 @@ export class AgentComposeClient {
|
|
|
755
788
|
// session's own `session-<uuid>` branch, where its unmerged work lives.
|
|
756
789
|
// Mutually exclusive with `revision` (the server rejects the combination).
|
|
757
790
|
if (opts?.branch !== undefined) q.set("branch", opts.branch);
|
|
791
|
+
// Browse-at-head (read-only): the file AS OF a retained 64-hex commit of
|
|
792
|
+
// the MAIN drive. A garbage-collected/unknown head answers 410
|
|
793
|
+
// `at_unavailable`.
|
|
794
|
+
if (opts?.at !== undefined) q.set("at", opts.at);
|
|
758
795
|
const buf = await this.fetch<ArrayBuffer, "arrayBuffer">(
|
|
759
796
|
`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q}`,
|
|
760
797
|
{ responseType: "arrayBuffer" },
|
|
@@ -767,7 +804,7 @@ export class AgentComposeClient {
|
|
|
767
804
|
* text corrupts the bytes irreversibly. */
|
|
768
805
|
async getFactoryFile(
|
|
769
806
|
path: string,
|
|
770
|
-
opts?: { factorySlug?: string; revision?: number; branch?: string },
|
|
807
|
+
opts?: { factorySlug?: string; revision?: number; branch?: string; at?: string },
|
|
771
808
|
): Promise<string> {
|
|
772
809
|
return new TextDecoder().decode(await this.getFactoryFileBytes(path, opts));
|
|
773
810
|
}
|
|
@@ -840,6 +877,31 @@ export class AgentComposeClient {
|
|
|
840
877
|
return this.fetch<SessionsPage>(`/api/v1/sessions?${q}`);
|
|
841
878
|
}
|
|
842
879
|
|
|
880
|
+
/** A session's sandbox perf HISTORY — 15-minute rollup buckets
|
|
881
|
+
* (CPU/mem/disk percentiles, load, saturation seconds), newest first.
|
|
882
|
+
* Member-gated like the session itself. Cursor-paginated. */
|
|
883
|
+
getSessionPerfHistory(
|
|
884
|
+
conversationId: string, opts?: { limit?: number; cursor?: string },
|
|
885
|
+
): Promise<SessionPerfHistoryPage> {
|
|
886
|
+
const q = new URLSearchParams();
|
|
887
|
+
if (opts?.limit !== undefined) q.set("limit", String(opts.limit));
|
|
888
|
+
if (opts?.cursor) q.set("cursor", opts.cursor);
|
|
889
|
+
return this.fetch<SessionPerfHistoryPage>(
|
|
890
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/perf-history${q.toString() ? `?${q}` : ""}`);
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** Team-level sandbox perf aggregates over a window (default trailing
|
|
894
|
+
* 24h, max 30 days). Aggregate-only — no per-session identifiers. */
|
|
895
|
+
getFactoryPerfSummary(
|
|
896
|
+
factorySlug: string, opts?: { from?: string; to?: string },
|
|
897
|
+
): Promise<FactoryPerfSummary> {
|
|
898
|
+
const q = new URLSearchParams();
|
|
899
|
+
if (opts?.from) q.set("from", opts.from);
|
|
900
|
+
if (opts?.to) q.set("to", opts.to);
|
|
901
|
+
return this.fetch<FactoryPerfSummary>(
|
|
902
|
+
`/api/v1/factories/${encodeURIComponent(factorySlug)}/perf-summary${q.toString() ? `?${q}` : ""}`);
|
|
903
|
+
}
|
|
904
|
+
|
|
843
905
|
/** One conversation with its latest page of messages. `limit` bounds the
|
|
844
906
|
* page (newest N) — metadata-only consumers (e.g. resolving the
|
|
845
907
|
* session's drive branch) pass 1 instead of pulling the full hydrate. */
|
|
@@ -944,6 +1006,51 @@ export class AgentComposeClient {
|
|
|
944
1006
|
);
|
|
945
1007
|
}
|
|
946
1008
|
|
|
1009
|
+
/** Message ANOTHER session's conversation AS the calling session
|
|
1010
|
+
* (`agentc session message @alias`). Session toolbelt credential ONLY —
|
|
1011
|
+
* the sender resolves from the key. `target` is an @alias (team-scoped)
|
|
1012
|
+
* or a conversation uuid. Unlike a channel post this WAKES the target's
|
|
1013
|
+
* turn machinery; the response's `turn` verdict says whether it started,
|
|
1014
|
+
* queued behind a live turn, or was parked by the agent-to-agent
|
|
1015
|
+
* exchange damper. Unknown alias → 404 with `candidates`. */
|
|
1016
|
+
sendSessionMessage(input: { target: string; text: string }): Promise<SessionDirectMessageSent> {
|
|
1017
|
+
return this.fetch<SessionDirectMessageSent>("/api/v1/session-messages", {
|
|
1018
|
+
method: "POST", body: input,
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/** Claim ANOTHER session's drive branch for the calling session
|
|
1023
|
+
* (`agentc branch claim @alias`) — transfers the branch's one-writer
|
|
1024
|
+
* authority: the target's own writes refuse honestly until release, and
|
|
1025
|
+
* the response carries the mount token the CLI uses to FUSE-mount the
|
|
1026
|
+
* branch in-guest. Session toolbelt credential only; same-owner sessions
|
|
1027
|
+
* only (cross-user claims refuse with the gap stated); refuses while the
|
|
1028
|
+
* target is mid-turn (409 `mid_turn`) or already claimed
|
|
1029
|
+
* (409 `already_claimed`). */
|
|
1030
|
+
claimSessionBranch(input: { target: string; reason?: string }): Promise<BranchClaimGranted> {
|
|
1031
|
+
return this.fetch<BranchClaimGranted>("/api/v1/session-branch-claims", {
|
|
1032
|
+
method: "POST", body: { target: input.target, ...(input.reason ? { reason: input.reason } : {}) },
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/** Release branch claims HELD by the calling session (`agentc branch
|
|
1037
|
+
* release [@alias]`). `target` narrows to one claim; omitted releases
|
|
1038
|
+
* them all. Idempotent — an empty `released` list means nothing was
|
|
1039
|
+
* held. */
|
|
1040
|
+
releaseSessionBranchClaims(input: { target?: string } = {}): Promise<BranchClaimsReleased> {
|
|
1041
|
+
return this.fetch<BranchClaimsReleased>("/api/v1/session-branch-claims/release", {
|
|
1042
|
+
method: "POST", body: { ...(input.target ? { target: input.target } : {}) },
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
/** One session's branch-claim state: the live claim ON its branch (null =
|
|
1047
|
+
* writable there) and the claims it HOLDS (`agentc branch status`, the
|
|
1048
|
+
* dashboard's Changes-panel badge). Any viewer with access. */
|
|
1049
|
+
getSessionBranchClaimState(conversationId: string): Promise<BranchClaimState> {
|
|
1050
|
+
return this.fetch<BranchClaimState>(
|
|
1051
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/branch-claim`);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
947
1054
|
// ── Cloud-session developer surface (ADR-0052) ────────────────────────────
|
|
948
1055
|
|
|
949
1056
|
/** Open a dev preview on a cloud session: expose a port a dev server is
|
|
@@ -984,6 +1091,28 @@ export class AgentComposeClient {
|
|
|
984
1091
|
return body.closed;
|
|
985
1092
|
}
|
|
986
1093
|
|
|
1094
|
+
/** Hold (or extend — the stamp is monotone) a cloud session's
|
|
1095
|
+
* background-work busy lease: while it is live, the between-turns
|
|
1096
|
+
* park/suspend leaves the session's VM running so work the turn left
|
|
1097
|
+
* 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> {
|
|
1100
|
+
return this.fetch<BackgroundWorkHeld>(
|
|
1101
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`,
|
|
1102
|
+
{ method: "POST", body: { ...(input?.minutes !== undefined ? { minutes: input.minutes } : {}) } },
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
/** 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> {
|
|
1109
|
+
const body = await this.fetch<{ released: boolean }>(
|
|
1110
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/background-work`,
|
|
1111
|
+
{ method: "DELETE" },
|
|
1112
|
+
);
|
|
1113
|
+
return body.released;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
987
1116
|
/** Fork a cloud session from HEAD: snapshot the VM + branch the drive + seed
|
|
988
1117
|
* a new conversation from the transcript so far, booting from both. Returns
|
|
989
1118
|
* the child conversation id to switch to. Write-tier; "Branch from here." */
|
|
@@ -1006,9 +1135,82 @@ export class AgentComposeClient {
|
|
|
1006
1135
|
* writes `main` directly. The list reflects the branch's last flush and
|
|
1007
1136
|
* is advisory; a merge re-verifies against fresh state. 404 = not found
|
|
1008
1137
|
* or not a member (uniform). */
|
|
1009
|
-
getSessionChanges(
|
|
1138
|
+
getSessionChanges(
|
|
1139
|
+
conversationId: string,
|
|
1140
|
+
opts: { after?: string; limit?: number } = {},
|
|
1141
|
+
): Promise<SessionChangeSet> {
|
|
1142
|
+
// Graph-plane pagination (additive): `after` = the previous page's
|
|
1143
|
+
// `nextAfter` cursor; `limit` 1..1000. Older servers ignore both.
|
|
1144
|
+
const q = new URLSearchParams();
|
|
1145
|
+
if (opts.after !== undefined) q.set("after", opts.after);
|
|
1146
|
+
if (opts.limit !== undefined) q.set("limit", String(opts.limit));
|
|
1147
|
+
const tail = q.toString();
|
|
1010
1148
|
return this.fetch<SessionChangeSet>(
|
|
1011
|
-
`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes`,
|
|
1149
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes${tail ? `?${tail}` : ""}`,
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/** One proposed file's RAW BYTES from a session's change set
|
|
1154
|
+
* (`GET /conversations/:id/changes/content`) — the review surface's
|
|
1155
|
+
* per-file read, also the in-sandbox `agentc review file` door.
|
|
1156
|
+
* `side: "base"` serves the PRE-change side (graph plane; the legacy
|
|
1157
|
+
* plane ignores it and always serves the proposed side). 404 = missing
|
|
1158
|
+
* path or no access (uniform, no existence oracle). */
|
|
1159
|
+
async getSessionChangeContent(
|
|
1160
|
+
conversationId: string, path: string, opts: { side?: "base" } = {},
|
|
1161
|
+
): Promise<Uint8Array> {
|
|
1162
|
+
const q = new URLSearchParams({ path });
|
|
1163
|
+
if (opts.side !== undefined) q.set("side", opts.side);
|
|
1164
|
+
const buf = await this.fetch<ArrayBuffer, "arrayBuffer">(
|
|
1165
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/content?${q}`,
|
|
1166
|
+
{ responseType: "arrayBuffer" },
|
|
1167
|
+
);
|
|
1168
|
+
return new Uint8Array(buf);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
/** Publish a diff review's WHOLE document — prose notes plus structured
|
|
1172
|
+
* suggestions, FULL REPLACE on every call (`POST
|
|
1173
|
+
* /conversations/:id/review-notes`; `agentc review publish`). Callable
|
|
1174
|
+
* ONLY with the bound review session's own toolbelt credential — any
|
|
1175
|
+
* other key (another session, a human cookie, a plain API key) refuses
|
|
1176
|
+
* 403. `conversationId` is the REVIEWED session's conversation (in a
|
|
1177
|
+
* review sandbox: `AGENT_COMPOSE_REVIEW_OF_CONVERSATION_ID`).
|
|
1178
|
+
*
|
|
1179
|
+
* Failures: 403 `session_credential_required` | `not_review_session` |
|
|
1180
|
+
* `review_superseded` (a newer review replaced this one); 400
|
|
1181
|
+
* `suggestion_path_not_in_change_set` (naming the offenders); 409
|
|
1182
|
+
* `review_disabled`. */
|
|
1183
|
+
publishReviewNotes(
|
|
1184
|
+
conversationId: string,
|
|
1185
|
+
input: { notes: string; suggestions?: ReviewSuggestionInput[] },
|
|
1186
|
+
): Promise<ReviewNotesPublished> {
|
|
1187
|
+
return this.fetch<ReviewNotesPublished>(
|
|
1188
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/review-notes`,
|
|
1189
|
+
{
|
|
1190
|
+
method: "POST",
|
|
1191
|
+
body: {
|
|
1192
|
+
notes: input.notes,
|
|
1193
|
+
...(input.suggestions !== undefined ? { suggestions: input.suggestions } : {}),
|
|
1194
|
+
},
|
|
1195
|
+
},
|
|
1196
|
+
);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/** Mint ONE fresh short-lived credential for the review git remote
|
|
1200
|
+
* (`POST /conversations/:id/review-git-credential`) — the server half
|
|
1201
|
+
* of the review sandbox's git credential helper (`agentc review
|
|
1202
|
+
* git-credential`), which calls this per git operation so the remote's
|
|
1203
|
+
* credential can never expire mid-review. Callable only with a review
|
|
1204
|
+
* session's own toolbelt credential; `conversationId` is the REVIEWED
|
|
1205
|
+
* session's conversation (`AGENT_COMPOSE_REVIEW_OF_CONVERSATION_ID`).
|
|
1206
|
+
*
|
|
1207
|
+
* Failures: 403 `session_credential_required` | `not_review_session`;
|
|
1208
|
+
* 409 `review_disabled` | `git_remote_unavailable` (legacy plane — read
|
|
1209
|
+
* the proposal through `getSessionChanges` instead). */
|
|
1210
|
+
getReviewGitCredential(conversationId: string): Promise<ReviewGitCredential> {
|
|
1211
|
+
return this.fetch<ReviewGitCredential>(
|
|
1212
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/review-git-credential`,
|
|
1213
|
+
{ method: "POST" },
|
|
1012
1214
|
);
|
|
1013
1215
|
}
|
|
1014
1216
|
|
|
@@ -1017,6 +1219,11 @@ export class AgentComposeClient {
|
|
|
1017
1219
|
* `main`. Write-tier + human caller. Pass `opts.branch` (the branch you
|
|
1018
1220
|
* reviewed) to fail 409 `branch_changed` if it moved since.
|
|
1019
1221
|
*
|
|
1222
|
+
* On a merge-GATED session (merge-gate spec) a non-approver's call does
|
|
1223
|
+
* NOT merge: it answers 202 `SessionMergeGated` — the ask froze into (or
|
|
1224
|
+
* converged on) a kind='merge' approval routed to the session's
|
|
1225
|
+
* approvers. Discriminate on `object`.
|
|
1226
|
+
*
|
|
1020
1227
|
* Failures: 403 `role_read_only` (read-only member) or a plain 403 for
|
|
1021
1228
|
* session toolbelt keys (agents cannot self-approve); 409 `turn_active`
|
|
1022
1229
|
* (a turn is running — retry when idle) | `branch_changed`; 400
|
|
@@ -1026,8 +1233,8 @@ export class AgentComposeClient {
|
|
|
1026
1233
|
mergeSessionChanges(
|
|
1027
1234
|
conversationId: string,
|
|
1028
1235
|
opts: { branch?: string } = {},
|
|
1029
|
-
): Promise<SessionMergeReport> {
|
|
1030
|
-
return this.fetch<SessionMergeReport>(
|
|
1236
|
+
): Promise<SessionMergeReport | SessionMergeGated> {
|
|
1237
|
+
return this.fetch<SessionMergeReport | SessionMergeGated>(
|
|
1031
1238
|
`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/merge`,
|
|
1032
1239
|
{ method: "POST", body: opts.branch ? { branch: opts.branch } : {} },
|
|
1033
1240
|
);
|
|
@@ -1048,6 +1255,59 @@ export class AgentComposeClient {
|
|
|
1048
1255
|
);
|
|
1049
1256
|
}
|
|
1050
1257
|
|
|
1258
|
+
/** The conflict PREFLIGHT: which of the session's changed files would
|
|
1259
|
+
* conflict with main if merged right now — the real merge's own per-file
|
|
1260
|
+
* classification, report-only (no writes, no locks). Read-tier.
|
|
1261
|
+
* Discriminate on `state`: `pending` means a cold compute is still
|
|
1262
|
+
* running — poll again to collect it; `unavailable` = no cheap anchors
|
|
1263
|
+
* (legacy plane / unbranched) — degrade honestly. */
|
|
1264
|
+
getSessionChangePreflight(conversationId: string): Promise<SessionChangePreflight> {
|
|
1265
|
+
return this.fetch<SessionChangePreflight>(
|
|
1266
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/preflight`,
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/** Rebase the session's branch ON main — fold main's current state
|
|
1271
|
+
* into the session's workspace (the reverse merge; main is never
|
|
1272
|
+
* touched). Conflicting files KEEP the session's version: each is
|
|
1273
|
+
* recorded as a conflict row and the session is told in-conversation to
|
|
1274
|
+
* reconcile. Write-tier — a session's own toolbelt key may rebase its
|
|
1275
|
+
* OWN branch (unlike merge/discard, which need a human). Pass
|
|
1276
|
+
* `opts.branch` to fail 409 `branch_changed` if it rotated since review.
|
|
1277
|
+
*
|
|
1278
|
+
* Failures: 409 `plane_unsupported` (legacy drive plane) |
|
|
1279
|
+
* `review_session_scratch` | `branch_moved` (the branch advanced
|
|
1280
|
+
* mid-rebase — retry) | `turn_active` | `decision_in_progress` |
|
|
1281
|
+
* `freshen_failed` / `merge_retry` (mount mid-handoff — retry); 400
|
|
1282
|
+
* `no_branch`; 502 `rebase_failed` (nothing changed — retry-safe). */
|
|
1283
|
+
rebaseSessionChanges(
|
|
1284
|
+
conversationId: string,
|
|
1285
|
+
opts: { branch?: string } = {},
|
|
1286
|
+
): Promise<SessionRebaseReport> {
|
|
1287
|
+
return this.fetch<SessionRebaseReport>(
|
|
1288
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/changes/rebase`,
|
|
1289
|
+
{ method: "POST", body: opts.branch ? { branch: opts.branch } : {} },
|
|
1290
|
+
);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
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
|
+
|
|
1051
1311
|
// ── Conversation membership (ADR-0045) ────────────────────────────────────
|
|
1052
1312
|
// Channel rosters carry roles (`owner > write > read`). Inviting is a
|
|
1053
1313
|
// `write` action; removing and re-roling are `owner` (or team admin)
|
|
@@ -1139,10 +1399,15 @@ export class AgentComposeClient {
|
|
|
1139
1399
|
* scoped, not surface-scoped: any member can cancel, whichever surface
|
|
1140
1400
|
* started the turn. The canceled turn closes with a terminal error part
|
|
1141
1401
|
* (it is never re-run); a message sent after it stays queued and is
|
|
1142
|
-
* answered next. `canceled: false` = nothing was in flight.
|
|
1402
|
+
* answered next. `canceled: false` = nothing was in flight. `owed`
|
|
1403
|
+
* reports the queued-backlog kick: 'dispatched' = a turn answering the
|
|
1404
|
+
* queue was started by this call; 'held' = a live release loop owns the
|
|
1405
|
+
* backlog; 'none' = nothing queued (absent on older servers). Requires
|
|
1143
1406
|
* the `invoke` scope. */
|
|
1144
|
-
cancelConversationTurn(
|
|
1145
|
-
|
|
1407
|
+
cancelConversationTurn(
|
|
1408
|
+
conversationId: string,
|
|
1409
|
+
): Promise<{ canceled: boolean; owed?: "dispatched" | "held" | "none" }> {
|
|
1410
|
+
return this.fetch<{ canceled: boolean; owed?: "dispatched" | "held" | "none" }>(
|
|
1146
1411
|
`/api/v1/conversations/${encodeURIComponent(conversationId)}/cancel-turn`,
|
|
1147
1412
|
{ method: "POST", body: {} },
|
|
1148
1413
|
);
|
|
@@ -1205,6 +1470,33 @@ export class AgentComposeClient {
|
|
|
1205
1470
|
);
|
|
1206
1471
|
}
|
|
1207
1472
|
|
|
1473
|
+
/** List a factory drive's files — a flat recursive listing, cursor-
|
|
1474
|
+
* paginated by path. The live index by default; pass `at` (a retained
|
|
1475
|
+
* 64-hex commit) to browse the MAIN drive as of that head — read-only
|
|
1476
|
+
* time travel, with `branch` + `at` pinning a graph branch instead. A
|
|
1477
|
+
* garbage-collected/unknown head throws 410 `at_unavailable`. */
|
|
1478
|
+
async listFactoryFiles(opts?: ListFactoryFilesOptions): Promise<FactoryFileListPage> {
|
|
1479
|
+
const factorySlug = opts?.factorySlug
|
|
1480
|
+
?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined)
|
|
1481
|
+
?? DEFAULT_FACTORY;
|
|
1482
|
+
const q = new URLSearchParams();
|
|
1483
|
+
if (opts?.prefix) q.set("prefix", opts.prefix);
|
|
1484
|
+
if (opts?.cursor) q.set("cursor", opts.cursor);
|
|
1485
|
+
if (opts?.limit != null) q.set("limit", String(opts.limit));
|
|
1486
|
+
if (opts?.branch !== undefined) q.set("branch", opts.branch);
|
|
1487
|
+
if (opts?.at !== undefined) q.set("at", opts.at);
|
|
1488
|
+
const qs = q.toString();
|
|
1489
|
+
const wire = await this.fetch<FactoryFileListWire>(
|
|
1490
|
+
`/api/v1/factories/${encodeURIComponent(factorySlug)}/files${qs ? `?${qs}` : ""}`,
|
|
1491
|
+
);
|
|
1492
|
+
return {
|
|
1493
|
+
data: wire.data,
|
|
1494
|
+
hasMore: wire.has_more,
|
|
1495
|
+
nextCursor: wire.next_cursor,
|
|
1496
|
+
at: wire.at ? { head: wire.at.head, committedAt: wire.at.committed_at } : null,
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1208
1500
|
/** List the human members of your team — the people you (or an agent) can
|
|
1209
1501
|
* @-flag with `createMentions`. Each row's `userId` is what
|
|
1210
1502
|
* `mentionedUserIds` expects. */
|
|
@@ -1608,17 +1900,18 @@ export class AgentComposeClient {
|
|
|
1608
1900
|
return body.links;
|
|
1609
1901
|
}
|
|
1610
1902
|
|
|
1611
|
-
/** Link a drive directory to a GitHub repo
|
|
1612
|
-
* scope)
|
|
1613
|
-
*
|
|
1903
|
+
/** Link a drive directory to a GitHub repo's tracked BRANCHES (`manage`
|
|
1904
|
+
* scope) — one link row per branch, created in one action. Requires the
|
|
1905
|
+
* drive to be graph-authoritative — 409 names the promotion
|
|
1906
|
+
* prerequisite otherwise. */
|
|
1614
1907
|
async createRepoLink(
|
|
1615
1908
|
input: CreateDriveRepoLinkInput, opts?: { factorySlug?: string },
|
|
1616
|
-
): Promise<DriveRepoLink> {
|
|
1909
|
+
): Promise<DriveRepoLink[]> {
|
|
1617
1910
|
const slug = opts?.factorySlug ?? DEFAULT_FACTORY;
|
|
1618
|
-
const body = await this.fetch<{
|
|
1911
|
+
const body = await this.fetch<{ links: DriveRepoLink[] }>(
|
|
1619
1912
|
`/api/v1/factories/${encodeURIComponent(slug)}/repo-links`,
|
|
1620
1913
|
{ method: "POST", body: input });
|
|
1621
|
-
return body.
|
|
1914
|
+
return body.links;
|
|
1622
1915
|
}
|
|
1623
1916
|
|
|
1624
1917
|
/** Unlink (§6.3: the prefix's files and history stay on the drive). */
|
|
@@ -1655,6 +1948,58 @@ export class AgentComposeClient {
|
|
|
1655
1948
|
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets/${encodeURIComponent(key)}`, { method: "DELETE" });
|
|
1656
1949
|
}
|
|
1657
1950
|
|
|
1951
|
+
// ── Session secrets ─────────────────────────────────────────────────────────
|
|
1952
|
+
// Per-session env for a cloud session's sandbox, keyed by the session's
|
|
1953
|
+
// CONVERSATION id. Values are write-only (set/rotate/delete — never read
|
|
1954
|
+
// back); each secret has an OWNER (the user who set it) and only the owner
|
|
1955
|
+
// or a team admin may replace/remove it. `source: "factory"` entries attach
|
|
1956
|
+
// a factory secret BY NAME instead of carrying a value. Changes apply from
|
|
1957
|
+
// the session's next turn.
|
|
1958
|
+
|
|
1959
|
+
/** List a session's secrets (names + metadata only — values are never returned). */
|
|
1960
|
+
async listSessionSecrets(conversationId: string): Promise<SessionSecretEntry[]> {
|
|
1961
|
+
const body = await this.fetch<{ secrets: SessionSecretEntry[] }>(
|
|
1962
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/secrets`);
|
|
1963
|
+
return body.secrets;
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
/** Set session secrets (bulk-friendly): value entries and/or `source:
|
|
1967
|
+
* "factory"` attachments-by-name. Returns the keys that were set. */
|
|
1968
|
+
async setSessionSecrets(conversationId: string, secrets: SessionSecretInput[]): Promise<string[]> {
|
|
1969
|
+
const body = await this.fetch<{ set: string[] }>(
|
|
1970
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/secrets`,
|
|
1971
|
+
{ method: "POST", body: { secrets } });
|
|
1972
|
+
return body.set;
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
/** Delete one session secret (owner or team admin). */
|
|
1976
|
+
deleteSessionSecret(conversationId: string, key: string): Promise<void> {
|
|
1977
|
+
return this.fetch(
|
|
1978
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/secrets/${encodeURIComponent(key)}`,
|
|
1979
|
+
{ method: "DELETE" });
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
/** Mint a VAULT LINK for this session — a short-lived, single-use page
|
|
1983
|
+
* where a session writer types the named credentials straight into the
|
|
1984
|
+
* session's secret store (they never transit the chat). Posts a card
|
|
1985
|
+
* into the session conversation; callable with the session's own
|
|
1986
|
+
* toolbelt credential (the in-sandbox agent's door) or by a session
|
|
1987
|
+
* writer. */
|
|
1988
|
+
requestSessionSecrets(
|
|
1989
|
+
conversationId: string, keys: string[], opts?: { reason?: string },
|
|
1990
|
+
): Promise<SessionSecretRequestCreated> {
|
|
1991
|
+
return this.fetch(
|
|
1992
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests`,
|
|
1993
|
+
{ method: "POST", body: { keys, ...(opts?.reason ? { reason: opts.reason } : {}) } });
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
/** Poll a vault link's status (`pending` → `fulfilled`; `expired` when the
|
|
1997
|
+
* clock ran out). The in-sandbox `agentc secrets request --wait` loop. */
|
|
1998
|
+
getSessionSecretRequest(conversationId: string, requestId: string): Promise<SessionSecretRequestStatus> {
|
|
1999
|
+
return this.fetch(
|
|
2000
|
+
`/api/v1/conversations/${encodeURIComponent(conversationId)}/secret-requests/${encodeURIComponent(requestId)}`);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
1658
2003
|
// ── API keys ───────────────────────────────────────────────────────────────
|
|
1659
2004
|
// Both endpoints require an admin-scoped key as the bearer token.
|
|
1660
2005
|
|
|
@@ -1668,10 +2013,17 @@ export class AgentComposeClient {
|
|
|
1668
2013
|
}
|
|
1669
2014
|
|
|
1670
2015
|
/** List API keys on the caller's team (metadata only — plaintext keys are
|
|
1671
|
-
* never returned).
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
2016
|
+
* never returned). Bounded + keyset-paginated: pass `cursor` from the
|
|
2017
|
+
* previous page's `nextCursor` to walk forward. */
|
|
2018
|
+
async listApiKeys(opts?: ListApiKeysOptions): Promise<ApiKeyPage> {
|
|
2019
|
+
const qs = new URLSearchParams();
|
|
2020
|
+
if (opts?.limit !== undefined) qs.set("limit", String(opts.limit));
|
|
2021
|
+
if (opts?.status !== undefined) qs.set("status", opts.status);
|
|
2022
|
+
if (opts?.cursor !== undefined) qs.set("cursor", opts.cursor);
|
|
2023
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
2024
|
+
const body = await this.fetch<{ object: "list"; data: ApiKey[]; has_more: boolean; next_cursor: string | null }>(
|
|
2025
|
+
`/api-keys${suffix}`);
|
|
2026
|
+
return { data: body.data, hasMore: body.has_more, nextCursor: body.next_cursor ?? null };
|
|
1675
2027
|
}
|
|
1676
2028
|
|
|
1677
2029
|
// ── Usage ──────────────────────────────────────────────────────────────────
|