@agent-compose/sdk 0.8.2 → 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 +164 -8
- package/dist/display.d.ts +17 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +1374 -51
- package/dist/runtimes/_cli-agent.d.ts +347 -2
- package/dist/runtimes/claude-code.d.ts +12 -0
- package/dist/runtimes/codex.d.ts +8 -0
- package/dist/runtimes/openai-desktop.js +1312 -51
- package/dist/runtimes/session-env.test.d.ts +14 -0
- package/dist/types/api-conversations.d.ts +309 -1
- package/dist/types/api-factory.d.ts +115 -10
- package/dist/types/api-runs.d.ts +21 -0
- package/dist/types/protocol.d.ts +32 -1
- package/dist/types/runtime.d.ts +120 -0
- package/package.json +1 -1
- package/src/agent/agent-context.ts +100 -11
- 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 +328 -12
- package/src/display.ts +44 -1
- package/src/index.ts +63 -1
- package/src/runtimes/_cli-agent.ts +891 -35
- package/src/runtimes/claude-code.ts +187 -12
- package/src/runtimes/codex.ts +58 -1
- package/src/sandbox/providers/local.ts +16 -4
- package/src/types/api-conversations.ts +307 -3
- package/src/types/api-factory.ts +118 -10
- package/src/types/api-runs.ts +23 -0
- package/src/types/protocol.ts +30 -1
- package/src/types/runtime.ts +122 -0
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";
|
|
@@ -33,9 +33,13 @@ import type {
|
|
|
33
33
|
CreateCloudSessionInput, CloudSessionCreated,
|
|
34
34
|
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
|
|
35
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,9 +53,10 @@ 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,
|
|
59
|
+
SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus,
|
|
55
60
|
CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage, UsageResponse,
|
|
56
61
|
DriveRepoLink, CreateDriveRepoLinkInput,
|
|
57
62
|
DriveMountSession, CreateDriveMountSessionInput,
|
|
@@ -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,
|
|
@@ -84,11 +90,15 @@ export type {
|
|
|
84
90
|
CreateCloudSessionInput, CloudSessionCreated, ConversationThread,
|
|
85
91
|
SessionPreview, PreviewOpened, OpenPreviewInput, SessionForked, BackgroundWorkHeld,
|
|
86
92
|
SessionFileChange, SessionChangeSet, SessionChangeStats, SessionDiffReviewBound, ReviewSuggestion,
|
|
93
|
+
ReviewSuggestionInput, ReviewNotesPublished, ReviewGitCredential, SessionReviewDocument,
|
|
87
94
|
ReviewSuggestionDecision, ReviewSuggestionDecisions,
|
|
88
95
|
SessionMergeReportDetail, SessionMergeReport, SessionMergeGated, SessionDiscardReport,
|
|
96
|
+
SessionPreflightFileRow, SessionChangePreflight, SessionRebaseReport, SessionAutoRebaseState,
|
|
89
97
|
ConversationPageContext, SendConversationMessageInput, ConversationTurnState,
|
|
90
98
|
SendConversationMessageResult, UnnotifiedMention, ConversationPresenceSnapshot, AgentListRow, StreamConversationOptions,
|
|
91
99
|
ChannelSessionStatus, ChannelSessionRow, ChannelSessionsResponse, SessionChannelMessagePosted,
|
|
100
|
+
SessionDirectMessageSent, BranchClaimInfo, BranchClaimGranted, BranchClaimsReleased, BranchClaimState,
|
|
101
|
+
SessionPerfBucket, SessionPerfHistoryPage,
|
|
92
102
|
} from "./types/api-conversations.js";
|
|
93
103
|
export type {
|
|
94
104
|
ConversationMemberRole, ConversationMember,
|
|
@@ -105,10 +115,11 @@ export type {
|
|
|
105
115
|
RegisterResult, RegisteredRuntime, RuntimeSourceInput, TemplateSourceRef, RegisterWorkflowInput,
|
|
106
116
|
TemplateRow, TemplateDetail, ListTemplatesOptions,
|
|
107
117
|
FactoryFileSearchRow, FactoryFolderSearchRow, SearchFactoryFilesOptions, FactoryFileSearchResult, FactoryFileWriteResult,
|
|
118
|
+
FactoryFileListRow, ListFactoryFilesOptions, FactoryFileListPage,
|
|
108
119
|
PublicFileLinkState,
|
|
109
|
-
FactoryRow, CreateFactoryInput, UpdateFactoryInput,
|
|
120
|
+
FactoryRow, CreateFactoryInput, UpdateFactoryInput, FactoryPerfSummary,
|
|
110
121
|
ScheduleRow, CreateScheduleInput,
|
|
111
|
-
SecretOptions, SetSecretResult, SecretListEntry,
|
|
122
|
+
SecretOptions, SetSecretResult, SecretListEntry, SessionSecretEntry, SessionSecretInput, SessionSecretRequestCreated, SessionSecretRequestStatus,
|
|
112
123
|
CreateApiKeyInput, ApiKey, ApiKeyCreated, ListApiKeysOptions, ApiKeyPage,
|
|
113
124
|
UsageRollupRow, UsageResponse,
|
|
114
125
|
DriveRepoLink, CreateDriveRepoLinkInput,
|
|
@@ -165,6 +176,17 @@ interface FactoryFileWriteWire {
|
|
|
165
176
|
created: boolean;
|
|
166
177
|
}
|
|
167
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
|
+
|
|
168
190
|
export interface AgentComposeClientOptions {
|
|
169
191
|
/** Your team's API key — minted from the dashboard or `agentc keys create`.
|
|
170
192
|
* Required. Resolved from `process.env.AGENT_COMPOSE_API_KEY` when omitted. */
|
|
@@ -653,6 +675,16 @@ export class AgentComposeClient {
|
|
|
653
675
|
);
|
|
654
676
|
}
|
|
655
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
|
+
|
|
656
688
|
/** Report a durable event against a run. Events are late-binding facts
|
|
657
689
|
* like quality.accepted, defect.regression, or intervention.override. */
|
|
658
690
|
async reportEvent(runId: string, input: ReportEventInput): Promise<EventRow> {
|
|
@@ -745,7 +777,7 @@ export class AgentComposeClient {
|
|
|
745
777
|
* U+FFFD and the original bytes are unrecoverable). */
|
|
746
778
|
async getFactoryFileBytes(
|
|
747
779
|
path: string,
|
|
748
|
-
opts?: { factorySlug?: string; revision?: number; branch?: string },
|
|
780
|
+
opts?: { factorySlug?: string; revision?: number; branch?: string; at?: string },
|
|
749
781
|
): Promise<Uint8Array> {
|
|
750
782
|
const factorySlug = opts?.factorySlug
|
|
751
783
|
?? (typeof process !== "undefined" ? process.env?.AGENT_COMPOSE_FACTORY : undefined)
|
|
@@ -756,6 +788,10 @@ export class AgentComposeClient {
|
|
|
756
788
|
// session's own `session-<uuid>` branch, where its unmerged work lives.
|
|
757
789
|
// Mutually exclusive with `revision` (the server rejects the combination).
|
|
758
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);
|
|
759
795
|
const buf = await this.fetch<ArrayBuffer, "arrayBuffer">(
|
|
760
796
|
`/api/v1/factories/${encodeURIComponent(factorySlug)}/files/content?${q}`,
|
|
761
797
|
{ responseType: "arrayBuffer" },
|
|
@@ -768,7 +804,7 @@ export class AgentComposeClient {
|
|
|
768
804
|
* text corrupts the bytes irreversibly. */
|
|
769
805
|
async getFactoryFile(
|
|
770
806
|
path: string,
|
|
771
|
-
opts?: { factorySlug?: string; revision?: number; branch?: string },
|
|
807
|
+
opts?: { factorySlug?: string; revision?: number; branch?: string; at?: string },
|
|
772
808
|
): Promise<string> {
|
|
773
809
|
return new TextDecoder().decode(await this.getFactoryFileBytes(path, opts));
|
|
774
810
|
}
|
|
@@ -841,6 +877,31 @@ export class AgentComposeClient {
|
|
|
841
877
|
return this.fetch<SessionsPage>(`/api/v1/sessions?${q}`);
|
|
842
878
|
}
|
|
843
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
|
+
|
|
844
905
|
/** One conversation with its latest page of messages. `limit` bounds the
|
|
845
906
|
* page (newest N) — metadata-only consumers (e.g. resolving the
|
|
846
907
|
* session's drive branch) pass 1 instead of pulling the full hydrate. */
|
|
@@ -945,6 +1006,51 @@ export class AgentComposeClient {
|
|
|
945
1006
|
);
|
|
946
1007
|
}
|
|
947
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
|
+
|
|
948
1054
|
// ── Cloud-session developer surface (ADR-0052) ────────────────────────────
|
|
949
1055
|
|
|
950
1056
|
/** Open a dev preview on a cloud session: expose a port a dev server is
|
|
@@ -1029,9 +1135,82 @@ export class AgentComposeClient {
|
|
|
1029
1135
|
* writes `main` directly. The list reflects the branch's last flush and
|
|
1030
1136
|
* is advisory; a merge re-verifies against fresh state. 404 = not found
|
|
1031
1137
|
* or not a member (uniform). */
|
|
1032
|
-
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();
|
|
1033
1148
|
return this.fetch<SessionChangeSet>(
|
|
1034
|
-
`/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" },
|
|
1035
1214
|
);
|
|
1036
1215
|
}
|
|
1037
1216
|
|
|
@@ -1076,6 +1255,59 @@ export class AgentComposeClient {
|
|
|
1076
1255
|
);
|
|
1077
1256
|
}
|
|
1078
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
|
+
|
|
1079
1311
|
// ── Conversation membership (ADR-0045) ────────────────────────────────────
|
|
1080
1312
|
// Channel rosters carry roles (`owner > write > read`). Inviting is a
|
|
1081
1313
|
// `write` action; removing and re-roling are `owner` (or team admin)
|
|
@@ -1167,10 +1399,15 @@ export class AgentComposeClient {
|
|
|
1167
1399
|
* scoped, not surface-scoped: any member can cancel, whichever surface
|
|
1168
1400
|
* started the turn. The canceled turn closes with a terminal error part
|
|
1169
1401
|
* (it is never re-run); a message sent after it stays queued and is
|
|
1170
|
-
* 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
|
|
1171
1406
|
* the `invoke` scope. */
|
|
1172
|
-
cancelConversationTurn(
|
|
1173
|
-
|
|
1407
|
+
cancelConversationTurn(
|
|
1408
|
+
conversationId: string,
|
|
1409
|
+
): Promise<{ canceled: boolean; owed?: "dispatched" | "held" | "none" }> {
|
|
1410
|
+
return this.fetch<{ canceled: boolean; owed?: "dispatched" | "held" | "none" }>(
|
|
1174
1411
|
`/api/v1/conversations/${encodeURIComponent(conversationId)}/cancel-turn`,
|
|
1175
1412
|
{ method: "POST", body: {} },
|
|
1176
1413
|
);
|
|
@@ -1233,6 +1470,33 @@ export class AgentComposeClient {
|
|
|
1233
1470
|
);
|
|
1234
1471
|
}
|
|
1235
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
|
+
|
|
1236
1500
|
/** List the human members of your team — the people you (or an agent) can
|
|
1237
1501
|
* @-flag with `createMentions`. Each row's `userId` is what
|
|
1238
1502
|
* `mentionedUserIds` expects. */
|
|
@@ -1684,6 +1948,58 @@ export class AgentComposeClient {
|
|
|
1684
1948
|
return this.fetch(`/api/v1/factories/${encodeURIComponent(factorySlug)}/secrets/${encodeURIComponent(key)}`, { method: "DELETE" });
|
|
1685
1949
|
}
|
|
1686
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
|
+
|
|
1687
2003
|
// ── API keys ───────────────────────────────────────────────────────────────
|
|
1688
2004
|
// Both endpoints require an admin-scoped key as the bearer token.
|
|
1689
2005
|
|
package/src/display.ts
CHANGED
|
@@ -70,7 +70,11 @@ export type DisplayMarker =
|
|
|
70
70
|
| { kind: "image"; path: string; factorySlug?: string }
|
|
71
71
|
| { kind: "table"; columns: DisplayTableColumn[]; rows: Array<Record<string, DisplayTableCell>>; truncated: boolean }
|
|
72
72
|
| { kind: "chart"; chartKind: DisplayChartKind; series: DisplayChartSeries[]; xLabel?: string; yLabel?: string }
|
|
73
|
-
| { kind: "ask"; askId: string; prompt: string; options?: DisplayAskOption[]; freeText: boolean }
|
|
73
|
+
| { kind: "ask"; askId: string; prompt: string; options?: DisplayAskOption[]; freeText: boolean }
|
|
74
|
+
/** A desktop snapshot (`agentc display desktop`): the CLI captured the
|
|
75
|
+
* session VM's screen to `path` on the session's drive branch. The card
|
|
76
|
+
* renders the snapshot + an "Open desktop" door to the live surface. */
|
|
77
|
+
| { kind: "desktop"; path: string; factorySlug?: string; capturedAt?: string; note?: string };
|
|
74
78
|
|
|
75
79
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
76
80
|
|
|
@@ -273,6 +277,9 @@ export function clampAskOptions(raw: unknown): DisplayAskOption[] {
|
|
|
273
277
|
return options;
|
|
274
278
|
}
|
|
275
279
|
|
|
280
|
+
/** Desktop-snapshot note bound — one caption line on the card. */
|
|
281
|
+
export const DESKTOP_NOTE_MAX_CHARS = 200;
|
|
282
|
+
|
|
276
283
|
/** One-line JSON marker — exactly what `agentc display` prints. */
|
|
277
284
|
export function serializeDisplayMarker(marker: DisplayMarker): string {
|
|
278
285
|
return JSON.stringify({ [DISPLAY_MARKER_KEY]: DISPLAY_MARKER_VERSION, ...marker });
|
|
@@ -350,6 +357,21 @@ export function parseDisplayMarker(line: string): DisplayMarker | null {
|
|
|
350
357
|
...(yLabel !== undefined ? { yLabel } : {}),
|
|
351
358
|
};
|
|
352
359
|
}
|
|
360
|
+
case "desktop": {
|
|
361
|
+
const path = str("path");
|
|
362
|
+
if (!path || !isPlausibleDrivePath(path)) return null;
|
|
363
|
+
// capturedAt must parse as a real instant — a garbage stamp is
|
|
364
|
+
// dropped rather than rendered as "Invalid Date" on the card.
|
|
365
|
+
const capturedAt = str("capturedAt");
|
|
366
|
+
const capturedOk = capturedAt !== undefined && Number.isFinite(Date.parse(capturedAt));
|
|
367
|
+
const note = str("note");
|
|
368
|
+
return {
|
|
369
|
+
kind: "desktop", path,
|
|
370
|
+
...(str("factorySlug") ? { factorySlug: str("factorySlug") } : {}),
|
|
371
|
+
...(capturedOk ? { capturedAt } : {}),
|
|
372
|
+
...(note !== undefined ? { note: note.slice(0, DESKTOP_NOTE_MAX_CHARS) } : {}),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
353
375
|
case "ask": {
|
|
354
376
|
const askId = str("askId");
|
|
355
377
|
const rawPrompt = str("prompt");
|
|
@@ -384,6 +406,9 @@ export type AgentcInvocation =
|
|
|
384
406
|
| { verb: "display-document"; path: string; factorySlug?: string }
|
|
385
407
|
| { verb: "display-plan" }
|
|
386
408
|
| { verb: "display-image"; path: string; factorySlug?: string }
|
|
409
|
+
/** `agentc display desktop` — the CLI captures the screen itself, so the
|
|
410
|
+
* call has no positional path; the marker in the result names the file. */
|
|
411
|
+
| { verb: "display-desktop" }
|
|
387
412
|
/** Directive verb — detection only arms the tool_call promotion; the
|
|
388
413
|
* result keeps its raw output so the directive marker reaches the cloud
|
|
389
414
|
* executor. Same for table `source:"file"`, diff, and ask-with-approver. */
|
|
@@ -432,6 +457,7 @@ const VALUE_FLAGS = new Set([
|
|
|
432
457
|
"--factory", "--input", "--entries", "--url", "--api-key", "--limit",
|
|
433
458
|
"--thread", "--content-type", "--file", "--out", "--revision", "--ended-at",
|
|
434
459
|
"--data", "--kind", "--from", "--to", "--prompt", "--options", "--approver",
|
|
460
|
+
"--note",
|
|
435
461
|
]);
|
|
436
462
|
|
|
437
463
|
/** Detect a known agentc invocation inside a shell command string. Returns
|
|
@@ -514,6 +540,8 @@ export function detectAgentcInvocation(command: string): AgentcInvocation | null
|
|
|
514
540
|
const path = drivePath(args[2]);
|
|
515
541
|
return path ? { verb: "display-image", path, ...withSlug } : null;
|
|
516
542
|
}
|
|
543
|
+
case "desktop":
|
|
544
|
+
return { verb: "display-desktop" };
|
|
517
545
|
case "preview": {
|
|
518
546
|
const path = drivePath(args[2]);
|
|
519
547
|
return path ? { verb: "display-preview", path } : null;
|
|
@@ -700,6 +728,8 @@ export function createDisplayPromoter(ctx: DisplayPromoterContext = {}): Display
|
|
|
700
728
|
return call("display_plan", {});
|
|
701
729
|
case "display-image":
|
|
702
730
|
return call("display_image", { path: invocation.path, ...factoryRef(invocation.factorySlug) });
|
|
731
|
+
case "display-desktop":
|
|
732
|
+
return call("display_desktop", {});
|
|
703
733
|
case "display-preview":
|
|
704
734
|
return call("display_preview", { path: invocation.path, ...factoryRef(undefined) });
|
|
705
735
|
case "display-table":
|
|
@@ -789,6 +819,19 @@ export function createDisplayPromoter(ctx: DisplayPromoterContext = {}): Display
|
|
|
789
819
|
...factoryRef(m?.factorySlug ?? inv.factorySlug),
|
|
790
820
|
})];
|
|
791
821
|
}
|
|
822
|
+
case "display-desktop": {
|
|
823
|
+
// No marker → the capture failed before it could name a file —
|
|
824
|
+
// keep the raw output (the CLI's own error is the feedback);
|
|
825
|
+
// a desktop card without a snapshot would lie.
|
|
826
|
+
const marker = findDisplayMarker(text);
|
|
827
|
+
if (marker?.kind !== "desktop") return [result(msg.output)];
|
|
828
|
+
return [result({
|
|
829
|
+
path: marker.path,
|
|
830
|
+
...factoryRef(marker.factorySlug),
|
|
831
|
+
capturedAt: marker.capturedAt ?? null,
|
|
832
|
+
note: marker.note ?? null,
|
|
833
|
+
})];
|
|
834
|
+
}
|
|
792
835
|
case "display-table": {
|
|
793
836
|
// File-sourced tables are a DIRECTIVE: keep the raw output so the
|
|
794
837
|
// marker inside it reaches the cloud executor (the real card pair).
|