@huynle/hub 0.7.3 → 0.8.0
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/CHANGELOG.md +229 -0
- package/Dockerfile +7 -2
- package/README.md +153 -24
- package/package.json +5 -3
- package/packages/claude-code/package.json +1 -1
- package/packages/claude-code/src/daemon.ts +8 -1
- package/packages/claude-code/src/launcher.ts +29 -9
- package/packages/claude-code/src/profile.ts +173 -0
- package/packages/claude-code/src/sessions.ts +22 -4
- package/packages/cli/dist/hub-cli.js +6474 -3470
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/artifact-jobs-mcp.ts +226 -0
- package/packages/cli/src/artifacts-mcp.ts +160 -0
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +20 -8
- package/packages/cli/src/bootstrap/enrollment/policy.ts +125 -12
- package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
- package/packages/cli/src/cli.ts +95 -13
- package/packages/cli/src/format.ts +48 -10
- package/packages/cli/src/http.ts +1 -0
- package/packages/cli/src/intents.ts +23 -0
- package/packages/cli/src/login.ts +20 -6
- package/packages/cli/src/mcp.ts +380 -9
- package/packages/cli/src/messaging.ts +3 -1
- package/packages/cli/src/setup/commands.ts +3 -2
- package/packages/cli/src/setup/runtime.ts +3 -2
- package/packages/cli/src/team-intake-mcp.ts +158 -0
- package/packages/cli/src/teams-mcp.ts +403 -0
- package/packages/cli/src/watchlist.ts +135 -0
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/profile.ts +163 -0
- package/packages/codex/src/sessions.ts +12 -0
- package/packages/codex/src/spoke.ts +19 -1
- package/packages/node/package.json +1 -1
- package/packages/node/src/config.ts +96 -2
- package/packages/node/src/opencode-warm.ts +325 -0
- package/packages/node/src/run.ts +27 -1
- package/packages/preset/package.json +1 -1
- package/packages/preset/src/profile.ts +34 -0
- package/packages/preset/src/spoke.ts +4 -0
- package/packages/protocol/package.json +1 -1
- package/packages/protocol/src/artifacts.ts +142 -0
- package/packages/protocol/src/concierge.ts +366 -0
- package/packages/protocol/src/contracts.ts +104 -0
- package/packages/protocol/src/graph.ts +70 -0
- package/packages/protocol/src/hub-workspace.ts +201 -0
- package/packages/protocol/src/index.ts +169 -6
- package/packages/protocol/src/launch.ts +22 -0
- package/packages/protocol/src/profiles.ts +301 -0
- package/packages/protocol/src/teams.ts +204 -0
- package/packages/runtime-openshell/package.json +1 -1
- package/packages/runtime-sdk/package.json +1 -1
- package/packages/sdk/package.json +1 -1
- package/packages/sdk/src/address.ts +50 -2
- package/packages/sdk/src/artifact-jobs.ts +194 -0
- package/packages/sdk/src/artifacts.ts +187 -0
- package/packages/sdk/src/client.ts +226 -5
- package/packages/sdk/src/concierge.ts +88 -0
- package/packages/sdk/src/errors.ts +3 -1
- package/packages/sdk/src/index.ts +5 -0
- package/packages/sdk/src/team-intake.ts +122 -0
- package/packages/sdk/src/teams.ts +346 -0
- package/packages/sdk/src/types.ts +548 -1
- package/packages/spoke-sdk/package.json +1 -1
- package/packages/spoke-sdk/src/index.ts +1 -0
- package/packages/spoke-sdk/src/profile-apply.ts +245 -0
- package/src/central/actions.ts +7 -0
- package/src/central/agent/acting-routes.ts +2 -1
- package/src/central/agent/acting-switch.ts +2 -1
- package/src/central/agent/acting.ts +215 -10
- package/src/central/agent/app-tokens.ts +3 -2
- package/src/central/agent/audit.ts +17 -0
- package/src/central/agent/body.ts +163 -0
- package/src/central/agent/decisions.ts +69 -2
- package/src/central/agent/delivery.ts +95 -15
- package/src/central/agent/directory-routes.ts +37 -7
- package/src/central/agent/directory.ts +70 -14
- package/src/central/agent/errors.ts +5 -0
- package/src/central/agent/events-routes.ts +21 -4
- package/src/central/agent/events.ts +11 -3
- package/src/central/agent/form-check.ts +85 -0
- package/src/central/agent/grant-requests.ts +2 -1
- package/src/central/agent/grant-routes.ts +4 -3
- package/src/central/agent/grants.ts +29 -2
- package/src/central/agent/idempotency.ts +133 -0
- package/src/central/agent/intent-routes.ts +116 -0
- package/src/central/agent/intents.ts +505 -0
- package/src/central/agent/interaction-graph-routes.ts +212 -0
- package/src/central/agent/interaction-graph.ts +865 -0
- package/src/central/agent/launch-engine.ts +16 -2
- package/src/central/agent/launch-routes.ts +6 -1
- package/src/central/agent/launch-spec.ts +14 -1
- package/src/central/agent/message-routes.ts +2 -1
- package/src/central/agent/messages.ts +27 -2
- package/src/central/agent/names.ts +12 -2
- package/src/central/agent/read-routes.ts +86 -19
- package/src/central/agent/routes.ts +176 -5
- package/src/central/agent/webhook-routes.ts +2 -1
- package/src/central/agent-data.ts +4 -0
- package/src/central/artifacts/access.ts +44 -0
- package/src/central/artifacts/blob-store.ts +113 -0
- package/src/central/artifacts/blob-support.ts +98 -0
- package/src/central/artifacts/events.ts +27 -0
- package/src/central/artifacts/host-caps.ts +162 -0
- package/src/central/artifacts/host.ts +315 -0
- package/src/central/artifacts/idempotency.ts +109 -0
- package/src/central/artifacts/jobs-routes.ts +773 -0
- package/src/central/artifacts/jobs.ts +344 -0
- package/src/central/artifacts/maintenance.ts +103 -0
- package/src/central/artifacts/routes.ts +360 -0
- package/src/central/artifacts/store.ts +776 -0
- package/src/central/artifacts/workspaces.ts +31 -0
- package/src/central/backend/file-blobs.ts +377 -0
- package/src/central/backend/index.ts +12 -3
- package/src/central/backend/memory-blobs.ts +116 -0
- package/src/central/backend/postgres-blobs.ts +513 -0
- package/src/central/backend/postgres-migrate.ts +33 -0
- package/src/central/backend/postgres-state.ts +19 -0
- package/src/central/backend/types.ts +8 -0
- package/src/central/concierge/activity.ts +142 -0
- package/src/central/concierge/arm.ts +63 -0
- package/src/central/concierge/board-render.ts +190 -0
- package/src/central/concierge/board-results.ts +169 -0
- package/src/central/concierge/board-routes.ts +339 -0
- package/src/central/concierge/board.ts +249 -0
- package/src/central/concierge/context.ts +221 -0
- package/src/central/concierge/entitlements.ts +13 -0
- package/src/central/concierge/guard.ts +120 -0
- package/src/central/concierge/inbound.ts +89 -0
- package/src/central/concierge/index.ts +84 -0
- package/src/central/concierge/notes.ts +41 -0
- package/src/central/concierge/notifier.ts +986 -0
- package/src/central/concierge/prompt.ts +130 -0
- package/src/central/concierge/proposals.ts +262 -0
- package/src/central/concierge/push-remap.ts +49 -0
- package/src/central/concierge/routes.ts +62 -0
- package/src/central/concierge/schedule.ts +113 -0
- package/src/central/concierge/service.ts +243 -0
- package/src/central/concierge/session-routes.ts +69 -0
- package/src/central/concierge/settings.ts +217 -0
- package/src/central/concierge/supervisor.ts +740 -0
- package/src/central/concierge/tiers.ts +260 -0
- package/src/central/concierge/watchlist-routes.ts +277 -0
- package/src/central/concierge/watchlist.ts +273 -0
- package/src/central/config.ts +7 -1
- package/src/central/http.ts +633 -50
- package/src/central/mcp-http.ts +10 -21
- package/src/central/profiles/apply.ts +333 -0
- package/src/central/profiles/content.ts +174 -0
- package/src/central/profiles/events.ts +40 -0
- package/src/central/profiles/idempotency.ts +14 -0
- package/src/central/profiles/import.ts +139 -0
- package/src/central/profiles/routes.ts +356 -0
- package/src/central/profiles/store.ts +597 -0
- package/src/central/profiles/usage-ports.ts +120 -0
- package/src/central/profiles/usage.ts +364 -0
- package/src/central/push.ts +7 -0
- package/src/central/registry.ts +43 -6
- package/src/central/store.ts +4 -0
- package/src/central/teams/collections.ts +34 -0
- package/src/central/teams/discovery.ts +346 -0
- package/src/central/teams/draft-edits.ts +46 -0
- package/src/central/teams/events.ts +46 -0
- package/src/central/teams/intake-routes.ts +100 -0
- package/src/central/teams/intake.ts +772 -0
- package/src/central/teams/policy.ts +231 -0
- package/src/central/teams/routes.ts +516 -0
- package/src/central/teams/seats.ts +272 -0
- package/src/central/teams/staffing.ts +888 -0
- package/src/central/teams/store.ts +667 -0
- package/src/central/teams/validation.ts +256 -0
- package/src/central/workspaces/board.ts +408 -0
- package/src/central/workspaces/checkpoints.ts +316 -0
- package/src/central/workspaces/coordinator.ts +841 -0
- package/src/central/workspaces/events.ts +85 -0
- package/src/central/workspaces/lifecycle.ts +93 -0
- package/src/central/workspaces/master.ts +490 -0
- package/src/central/workspaces/members.ts +632 -0
- package/src/central/workspaces/routes.ts +354 -0
- package/src/central/workspaces/sidechats-routes.ts +410 -0
- package/src/central/workspaces/sidechats.ts +482 -0
- package/src/central/workspaces/store.ts +571 -0
- package/src/central/workspaces/supervision.ts +355 -0
- package/src/index.ts +97 -15
- package/src/spoke/concierge-agent.ts +33 -0
- package/src/spoke/deliver.ts +22 -4
- package/src/spoke/fork-revert.ts +142 -0
- package/src/spoke/hub-rpc.ts +41 -5
- package/src/spoke/hub-tool.ts +383 -18
- package/src/spoke/listing.ts +47 -4
- package/src/spoke/profile-apply.ts +298 -0
- package/src/spoke/session-context.ts +176 -0
- package/src/spoke/spoke.ts +319 -20
- package/src/spoke/timeline.ts +6 -1
- package/src/spoke/tracker.ts +23 -1
- package/src/spoke/who-teams.ts +81 -0
- package/src/tui-messaging.ts +32 -18
- package/web/dist/assets/ArtifactPage-BS0gVkfu.js +1 -0
- package/web/dist/assets/ArtifactPane-PLYHNs6s.js +1 -0
- package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
- package/web/dist/assets/ArtifactWorkbench-LkDyJ5Dv.js +6 -0
- package/web/dist/assets/Library-CtxRqTjo.js +2 -0
- package/web/dist/assets/Library-DYsFj_5c.css +1 -0
- package/web/dist/assets/ProfilesDestination-BYiHKLmc.js +1 -0
- package/web/dist/assets/TeamsPage-BwjsAAUC.js +1 -0
- package/web/dist/assets/Watchtower-C4swrWVs.js +2 -0
- package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
- package/web/dist/assets/index-DaH5GUdV.js +31 -0
- package/web/dist/assets/index-WWuKfWxL.css +1 -0
- package/web/dist/assets/live-BUofsQpz.css +1 -0
- package/web/dist/assets/live-YyTjE47g.js +5 -0
- package/web/dist/assets/pane-layout-CKC4I00k.js +1 -0
- package/web/dist/assets/plural-Bspvoqfq.js +1 -0
- package/web/dist/assets/renderer2d-CX5j3NcD.js +1 -0
- package/web/dist/assets/renderer3d-BXIVOvfi.js +5393 -0
- package/web/dist/assets/safe-area-DfPsOdWR.js +1 -0
- package/web/dist/assets/source-CvUHTc3O.js +1 -0
- package/web/dist/assets/ui-C9Igxzjm.js +1 -0
- package/web/dist/index.html +5 -2
- package/web/dist/sw.js +22 -2
- package/web/dist/assets/index-Dm8OmkaO.css +0 -1
- package/web/dist/assets/index-FUJbj_Du.js +0 -20
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// HubError (the envelope) or HubTransportError (errors.ts). Typed verbs are added on top of call()/request().
|
|
3
3
|
import { envelopeOf, HubError, HubTransportError } from "./errors.ts"
|
|
4
4
|
import { sseEvents } from "./sse.ts"
|
|
5
|
+
import { type Artifacts, artifactsApi } from "./artifacts.ts"
|
|
6
|
+
import { type Teams, teamsApi } from "./teams.ts"
|
|
7
|
+
import { type Concierge, conciergeApi } from "./concierge.ts"
|
|
5
8
|
import type {
|
|
6
9
|
Approved,
|
|
7
10
|
AppTokenView,
|
|
@@ -18,6 +21,7 @@ import type {
|
|
|
18
21
|
DecisionUrgency,
|
|
19
22
|
Denied,
|
|
20
23
|
DirectoryEntry,
|
|
24
|
+
WhoAnswer,
|
|
21
25
|
FormAnswer,
|
|
22
26
|
FormAnswered,
|
|
23
27
|
Grant,
|
|
@@ -34,6 +38,7 @@ import type {
|
|
|
34
38
|
PromptDelivery,
|
|
35
39
|
Prompted,
|
|
36
40
|
Pinned,
|
|
41
|
+
SessionIntent,
|
|
37
42
|
Replied,
|
|
38
43
|
RoleClaim,
|
|
39
44
|
RoleRelease,
|
|
@@ -48,6 +53,41 @@ import type {
|
|
|
48
53
|
Whoami,
|
|
49
54
|
WebhookDelivery,
|
|
50
55
|
WebhookView,
|
|
56
|
+
ProfileAssignment,
|
|
57
|
+
ProfileContent,
|
|
58
|
+
ProfileCreated,
|
|
59
|
+
ProfileListInclude,
|
|
60
|
+
ProfileListPage,
|
|
61
|
+
ProfilePage,
|
|
62
|
+
ProfileUsage,
|
|
63
|
+
ProfileRendered,
|
|
64
|
+
ProfileSkillLoaded,
|
|
65
|
+
ProfileState,
|
|
66
|
+
ProfileVersion,
|
|
67
|
+
ProfileVersionEntry,
|
|
68
|
+
ProfileVersionSummary,
|
|
69
|
+
ProfileView,
|
|
70
|
+
SessionProfile,
|
|
71
|
+
WorkspaceArchived,
|
|
72
|
+
WorkspaceAssignmentRecord,
|
|
73
|
+
WorkspaceAssignmentStatus,
|
|
74
|
+
WorkspaceAssignmentView,
|
|
75
|
+
WorkspaceBinding,
|
|
76
|
+
WorkspaceBoard,
|
|
77
|
+
WorkspaceDeparture,
|
|
78
|
+
WorkspaceMasterReplaced,
|
|
79
|
+
WorkspaceMasterView,
|
|
80
|
+
WorkspaceMemberRecord,
|
|
81
|
+
WorkspaceMemberView,
|
|
82
|
+
WorkspacePage,
|
|
83
|
+
WorkspaceRole,
|
|
84
|
+
WorkspaceSideChat,
|
|
85
|
+
WorkspaceSideChatDelivery,
|
|
86
|
+
WorkspaceSideChatSummary,
|
|
87
|
+
WorkspaceState,
|
|
88
|
+
WorkspaceSupervised,
|
|
89
|
+
WorkspaceSupervisionGraph,
|
|
90
|
+
WorkspaceView,
|
|
51
91
|
} from "./types.ts"
|
|
52
92
|
|
|
53
93
|
const AGENT = "/api/agent"
|
|
@@ -62,7 +102,11 @@ export interface RequestOptions {
|
|
|
62
102
|
readonly query?: Readonly<Record<string, QueryValue>>
|
|
63
103
|
/** Sent as JSON. */
|
|
64
104
|
readonly body?: unknown
|
|
105
|
+
/** Sent as is with its own content type, instead of a JSON body (artifact blob uploads). */
|
|
106
|
+
readonly raw?: { readonly body: BodyInit; readonly contentType: string }
|
|
65
107
|
readonly signal?: AbortSignal
|
|
108
|
+
/** Extra request headers (e.g. Idempotency-Key); authorization and content headers are the client's. */
|
|
109
|
+
readonly headers?: Readonly<Record<string, string>>
|
|
66
110
|
}
|
|
67
111
|
|
|
68
112
|
/** The part of fetch the client uses. */
|
|
@@ -106,6 +150,8 @@ export interface SessionsFilter extends CallOptions {
|
|
|
106
150
|
readonly machine?: string
|
|
107
151
|
/** `key=value` labels, all of which must match. */
|
|
108
152
|
readonly label?: readonly string[]
|
|
153
|
+
/** (CON-11) Words that must all appear in the title or @name (case and punctuation ignored; ≤ 200 characters). */
|
|
154
|
+
readonly q?: string
|
|
109
155
|
}
|
|
110
156
|
|
|
111
157
|
export interface LaunchesFilter extends CallOptions {
|
|
@@ -126,6 +172,105 @@ export interface Launches {
|
|
|
126
172
|
stop(id: string, options?: CallOptions): Promise<{ launch: LaunchView }>
|
|
127
173
|
}
|
|
128
174
|
|
|
175
|
+
export interface IdempotentOptions extends CallOptions {
|
|
176
|
+
/** The same key and body within 24 h answer the first result again (Idempotency-Key). */
|
|
177
|
+
readonly idempotencyKey?: string
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface ProfilesFilter extends CallOptions {
|
|
181
|
+
readonly state?: ProfileState | "all"
|
|
182
|
+
readonly limit?: number
|
|
183
|
+
readonly cursor?: string
|
|
184
|
+
/** (RP-08) "usage": per-profile usage counts (ProfileListPage.usage); "summary": latest version counts (summaries). */
|
|
185
|
+
readonly include?: ProfileListInclude | readonly ProfileListInclude[]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* hub.profiles (HWA-14): versioned agent profiles (instructions, skills, commands). Users; app tokens with
|
|
190
|
+
* profiles:read / profiles:manage / profiles:assign (session routes also need sessions:read); session tokens read
|
|
191
|
+
* only their own pinned profile (ofSession, sessionSkill, sessionRender). Mutations take expectedRevision; a stale
|
|
192
|
+
* one is HubError conflict with envelope.currentRevision.
|
|
193
|
+
*/
|
|
194
|
+
export interface Profiles {
|
|
195
|
+
create(input: { readonly name: string; readonly title: string; readonly description?: string; readonly tags?: readonly string[]; readonly metadata?: Readonly<Record<string, string>>; readonly projects?: readonly string[]; readonly content: ProfileContent }, options?: IdempotentOptions): Promise<ProfileCreated>
|
|
196
|
+
/** From AGENTS.md / skills/<name>/SKILL.md / commands/<name>.md text files; anything else is unsupported. */
|
|
197
|
+
import(input: { readonly name: string; readonly title: string; readonly description?: string; readonly tags?: readonly string[]; readonly files: readonly { readonly path: string; readonly text: string }[] }, options?: IdempotentOptions): Promise<ProfileCreated>
|
|
198
|
+
list(filter?: ProfilesFilter): Promise<ProfileListPage>
|
|
199
|
+
get(id: string, options?: CallOptions): Promise<{ profile: ProfileView; latest?: ProfileVersionSummary }>
|
|
200
|
+
/** metadata/projects replace the stored ones; null clears them (RP-08). */
|
|
201
|
+
update(
|
|
202
|
+
id: string,
|
|
203
|
+
patch: { readonly expectedRevision: number; readonly title?: string; readonly description?: string | null; readonly tags?: readonly string[]; readonly state?: ProfileState; readonly metadata?: Readonly<Record<string, string>> | null; readonly projects?: readonly string[] | null },
|
|
204
|
+
options?: CallOptions,
|
|
205
|
+
): Promise<{ profile: ProfileView }>
|
|
206
|
+
/** (RP-08) Where the profile is used: pinned sessions (paged), team roles, launches, workspaces; per-category scopes. */
|
|
207
|
+
usage(id: string, options?: CallOptions & { readonly limit?: number; readonly cursor?: string }): Promise<ProfileUsage>
|
|
208
|
+
versions(id: string, options?: CallOptions & { readonly limit?: number; readonly cursor?: string }): Promise<ProfilePage<ProfileVersionEntry>>
|
|
209
|
+
/** A new immutable version from content, an older version (explicit rollback) or files. */
|
|
210
|
+
createVersion(id: string, input: { readonly expectedRevision: number } & ({ readonly content: ProfileContent } | { readonly fromVersion: number } | { readonly files: readonly { readonly path: string; readonly text: string }[] }), options?: IdempotentOptions): Promise<ProfileCreated>
|
|
211
|
+
version(id: string, version: number, options?: CallOptions): Promise<{ version: ProfileVersion }>
|
|
212
|
+
skill(id: string, version: number, name: string, options?: CallOptions): Promise<ProfileSkillLoaded>
|
|
213
|
+
render(id: string, version: number, name: string, params: Readonly<Record<string, string>>, options?: CallOptions): Promise<ProfileRendered>
|
|
214
|
+
ofSession(addr: string, options?: CallOptions): Promise<SessionProfile>
|
|
215
|
+
assign(addr: string, input: { readonly profileID: string; readonly version: number; readonly expectedRevision: number }, options?: CallOptions): Promise<{ assignment: ProfileAssignment }>
|
|
216
|
+
unassign(addr: string, expectedRevision: number, options?: CallOptions): Promise<void>
|
|
217
|
+
sessionSkill(addr: string, name: string, options?: CallOptions): Promise<ProfileSkillLoaded>
|
|
218
|
+
sessionRender(addr: string, name: string, params: Readonly<Record<string, string>>, options?: CallOptions): Promise<ProfileRendered>
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* hub.workspaces (HWA-06): coordination workspaces with bindings, members, assignments and supervision. Users;
|
|
223
|
+
* app tokens with workspaces:read / workspaces:manage (member and assignment routes also need sessions:read);
|
|
224
|
+
* session tokens are refused. Every change of an existing record takes expectedRevision (supervise: the graph's
|
|
225
|
+
* revision from supervision()); a stale one is HubError conflict with envelope.currentRevision. Nothing here
|
|
226
|
+
* grants authority.
|
|
227
|
+
*/
|
|
228
|
+
export interface Workspaces {
|
|
229
|
+
create(input: { readonly title: string; readonly description?: string; readonly tags?: readonly string[]; readonly metadata?: Readonly<Record<string, string>> }, options?: IdempotentOptions): Promise<{ workspace: WorkspaceView }>
|
|
230
|
+
list(filter?: CallOptions & { readonly state?: WorkspaceState; readonly limit?: number; readonly cursor?: string }): Promise<WorkspacePage<WorkspaceView>>
|
|
231
|
+
get(id: string, options?: CallOptions): Promise<{ workspace: WorkspaceView; bindings: readonly WorkspaceBinding[] }>
|
|
232
|
+
update(id: string, patch: { readonly expectedRevision: number; readonly title?: string; readonly description?: string | null; readonly tags?: readonly string[] | null; readonly metadata?: Readonly<Record<string, string>> | null }, options?: CallOptions): Promise<{ workspace: WorkspaceView }>
|
|
233
|
+
/** Archives it: current members depart (`archived`) and their supervision links are detached. */
|
|
234
|
+
archive(id: string, expectedRevision: number, options?: CallOptions): Promise<WorkspaceArchived>
|
|
235
|
+
restore(id: string, expectedRevision: number, options?: CallOptions): Promise<{ workspace: WorkspaceView }>
|
|
236
|
+
bindings(id: string, options?: CallOptions): Promise<{ bindings: readonly WorkspaceBinding[] }>
|
|
237
|
+
bind(id: string, input: { readonly machineID: string; readonly repo: string; readonly label?: string }, options?: IdempotentOptions): Promise<{ binding: WorkspaceBinding; created: boolean }>
|
|
238
|
+
unbind(id: string, bindingID: string, expectedRevision: number, options?: CallOptions): Promise<void>
|
|
239
|
+
members(id: string, options?: CallOptions & { readonly includeLeft?: boolean }): Promise<{ members: readonly WorkspaceMemberView[] }>
|
|
240
|
+
join(id: string, input: { readonly sessionID: string; readonly role: Exclude<WorkspaceRole, "master"> }, options?: IdempotentOptions): Promise<{ member: WorkspaceMemberRecord; created: boolean }>
|
|
241
|
+
setRole(id: string, memberID: string, role: Exclude<WorkspaceRole, "master">, expectedRevision: number, options?: CallOptions): Promise<{ member: WorkspaceMemberRecord }>
|
|
242
|
+
/** Removes a member (`removed`) and detaches its supervision links. */
|
|
243
|
+
leave(id: string, memberID: string, expectedRevision: number, options?: CallOptions): Promise<WorkspaceDeparture>
|
|
244
|
+
assignments(id: string, filter?: CallOptions & { readonly memberID?: string; readonly status?: WorkspaceAssignmentStatus; readonly limit?: number; readonly cursor?: string }): Promise<WorkspacePage<WorkspaceAssignmentView>>
|
|
245
|
+
assignment(id: string, assignmentID: string, options?: CallOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
|
|
246
|
+
assign(id: string, input: { readonly memberID: string; readonly title: string; readonly status?: "open" | "in_progress" | "blocked"; readonly criteria?: readonly string[]; readonly ref?: string }, options?: IdempotentOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
|
|
247
|
+
setAssignmentStatus(id: string, assignmentID: string, status: WorkspaceAssignmentStatus, expectedRevision: number, options?: CallOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
|
|
248
|
+
reassign(id: string, assignmentID: string, memberID: string, expectedRevision: number, options?: CallOptions): Promise<{ assignment: WorkspaceAssignmentRecord }>
|
|
249
|
+
supervision(id: string, options?: CallOptions): Promise<WorkspaceSupervisionGraph>
|
|
250
|
+
/** Attaches, moves, reorders or (supervisorMemberID null) detaches a member's supervisor. */
|
|
251
|
+
supervise(id: string, input: { readonly memberID: string; readonly supervisorMemberID: string | null; readonly order?: number; readonly expectedRevision: number }, options?: CallOptions): Promise<WorkspaceSupervised>
|
|
252
|
+
/** The situation board (HWA-10): bounded, read-only; apps also need sessions:read. */
|
|
253
|
+
board(id: string, options?: CallOptions): Promise<WorkspaceBoard>
|
|
254
|
+
/** The current master, earlier masters and a replacement in flight (HWA-12); apps also need sessions:read. */
|
|
255
|
+
master(id: string, options?: CallOptions): Promise<WorkspaceMasterView>
|
|
256
|
+
/** Replaces (or first sets) the master under the workspace revision; sessionID or a running launch's launchID. No grants move. */
|
|
257
|
+
replaceMaster(id: string, input: ({ readonly sessionID: string } | { readonly launchID: string }) & { readonly expectedRevision: number }, options?: IdempotentOptions): Promise<WorkspaceMasterReplaced>
|
|
258
|
+
/**
|
|
259
|
+
* Side chats (HWA-13): a new session launched (through the launch API, under your launch authority) on a host
|
|
260
|
+
* binding of the workspace, started with a bounded copy of the source's messages (none named: the newest).
|
|
261
|
+
* Apps also need sessions:read, messages:read and the launch scope and grant.
|
|
262
|
+
*/
|
|
263
|
+
createSideChat(id: string, input: { readonly sourceSessionID: string; readonly sources?: readonly { readonly kind: "message"; readonly id: string }[]; readonly bindingID?: string; readonly harness?: string; readonly model?: string; readonly agent?: string }, options?: IdempotentOptions): Promise<{ sideChat: WorkspaceSideChat }>
|
|
264
|
+
sideChats(id: string, filter?: CallOptions & { readonly limit?: number; readonly cursor?: string }): Promise<WorkspacePage<WorkspaceSideChatSummary>>
|
|
265
|
+
sideChat(id: string, sideChatID: string, options?: CallOptions): Promise<{ sideChat: WorkspaceSideChat }>
|
|
266
|
+
/** A side chat by its ID alone (panes know only that). */
|
|
267
|
+
sideChatByID(sideChatID: string, options?: CallOptions): Promise<{ sideChat: WorkspaceSideChat }>
|
|
268
|
+
/** Re-reads the source (ACL checked again) and sends the new context to the side-chat session as a queued message. */
|
|
269
|
+
refreshSideChat(id: string, sideChatID: string, expectedRevision: number, options?: CallOptions): Promise<{ sideChat: WorkspaceSideChat; delivery: WorkspaceSideChatDelivery }>
|
|
270
|
+
/** Users only: a confirmed, queued message to the source session (never steers its running turn). */
|
|
271
|
+
sendToMain(id: string, sideChatID: string, text: string, options?: CallOptions): Promise<{ sideChatID: string; sent: { message: unknown; delivery?: string } }>
|
|
272
|
+
}
|
|
273
|
+
|
|
129
274
|
export interface TargetOptions extends CallOptions {
|
|
130
275
|
/** Act on this session (address) instead of the caller's own. */
|
|
131
276
|
readonly session?: string
|
|
@@ -189,6 +334,7 @@ export interface PromptOptions extends ActOptions {
|
|
|
189
334
|
const whyField = (options: ActOptions) => (options.why !== undefined ? { why: options.why } : {})
|
|
190
335
|
|
|
191
336
|
const signalOf = (options: CallOptions) => (options.signal ? { signal: options.signal } : {})
|
|
337
|
+
const idempotent = (options: IdempotentOptions) => ({ ...signalOf(options), ...(options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {}) })
|
|
192
338
|
/** The wake and urgent fields of a send, when given. */
|
|
193
339
|
const sendFields = (options: MsgOptions) => ({ ...(options.wake !== undefined ? { wake: options.wake } : {}), ...(options.urgent !== undefined ? { urgent: options.urgent } : {}) })
|
|
194
340
|
/** The session and project fields of a role request, when given. */
|
|
@@ -216,6 +362,66 @@ export class HubClient {
|
|
|
216
362
|
stop: (id, options = {}) => this.call("DELETE", `${AGENT}/launches/${segment(id)}`, signalOf(options)),
|
|
217
363
|
}
|
|
218
364
|
|
|
365
|
+
/** Profiles (HWA-14). */
|
|
366
|
+
readonly profiles: Profiles = {
|
|
367
|
+
create: (input, options = {}) => this.call("POST", `${AGENT}/profiles`, { body: input, ...idempotent(options) }),
|
|
368
|
+
import: (input, options = {}) => this.call("POST", `${AGENT}/profiles/import`, { body: input, ...idempotent(options) }),
|
|
369
|
+
list: (filter = {}) => this.call("GET", `${AGENT}/profiles`, { query: { state: filter.state, limit: filter.limit, cursor: filter.cursor, include: filter.include }, ...signalOf(filter) }),
|
|
370
|
+
get: (id, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}`, signalOf(options)),
|
|
371
|
+
update: (id, patch, options = {}) => this.call("PATCH", `${AGENT}/profiles/${segment(id)}`, { body: patch, ...signalOf(options) }),
|
|
372
|
+
usage: (id, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/usage`, { query: { limit: options.limit, cursor: options.cursor }, ...signalOf(options) }),
|
|
373
|
+
versions: (id, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/versions`, { query: { limit: options.limit, cursor: options.cursor }, ...signalOf(options) }),
|
|
374
|
+
createVersion: (id, input, options = {}) => this.call("POST", `${AGENT}/profiles/${segment(id)}/versions`, { body: input, ...idempotent(options) }),
|
|
375
|
+
version: (id, version, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/versions/${version}`, signalOf(options)),
|
|
376
|
+
skill: (id, version, name, options = {}) => this.call("GET", `${AGENT}/profiles/${segment(id)}/versions/${version}/skills/${segment(name)}`, signalOf(options)),
|
|
377
|
+
render: (id, version, name, params, options = {}) => this.call("POST", `${AGENT}/profiles/${segment(id)}/versions/${version}/commands/${segment(name)}/render`, { body: { params }, ...signalOf(options) }),
|
|
378
|
+
ofSession: (addr, options = {}) => this.call("GET", `${AGENT}/sessions/${segment(addr)}/profile`, signalOf(options)),
|
|
379
|
+
assign: (addr, input, options = {}) => this.call("PUT", `${AGENT}/sessions/${segment(addr)}/profile`, { body: input, ...signalOf(options) }),
|
|
380
|
+
unassign: (addr, expectedRevision, options = {}) => this.call("DELETE", `${AGENT}/sessions/${segment(addr)}/profile`, { query: { expectedRevision }, ...signalOf(options) }),
|
|
381
|
+
sessionSkill: (addr, name, options = {}) => this.call("GET", `${AGENT}/sessions/${segment(addr)}/profile/skills/${segment(name)}`, signalOf(options)),
|
|
382
|
+
sessionRender: (addr, name, params, options = {}) => this.call("POST", `${AGENT}/sessions/${segment(addr)}/profile/commands/${segment(name)}/render`, { body: { params }, ...signalOf(options) }),
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/** Workspaces (HWA-06). */
|
|
386
|
+
readonly workspaces: Workspaces = {
|
|
387
|
+
create: (input, options = {}) => this.call("POST", `${AGENT}/workspaces`, { body: input, ...idempotent(options) }),
|
|
388
|
+
list: (filter = {}) => this.call("GET", `${AGENT}/workspaces`, { query: { state: filter.state, limit: filter.limit, cursor: filter.cursor }, ...signalOf(filter) }),
|
|
389
|
+
get: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}`, signalOf(options)),
|
|
390
|
+
update: (id, patch, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}`, { body: patch, ...signalOf(options) }),
|
|
391
|
+
archive: (id, expectedRevision, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/archive`, { body: { expectedRevision }, ...signalOf(options) }),
|
|
392
|
+
restore: (id, expectedRevision, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/restore`, { body: { expectedRevision }, ...signalOf(options) }),
|
|
393
|
+
bindings: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/bindings`, signalOf(options)),
|
|
394
|
+
bind: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/bindings`, { body: input, ...idempotent(options) }),
|
|
395
|
+
unbind: (id, bindingID, expectedRevision, options = {}) => this.call("DELETE", `${AGENT}/workspaces/${segment(id)}/bindings`, { query: { bindingID, expectedRevision }, ...signalOf(options) }),
|
|
396
|
+
members: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/members`, { query: { includeLeft: options.includeLeft ? 1 : undefined }, ...signalOf(options) }),
|
|
397
|
+
join: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/members`, { body: input, ...idempotent(options) }),
|
|
398
|
+
setRole: (id, memberID, role, expectedRevision, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}/members/${segment(memberID)}`, { body: { role, expectedRevision }, ...signalOf(options) }),
|
|
399
|
+
leave: (id, memberID, expectedRevision, options = {}) => this.call("DELETE", `${AGENT}/workspaces/${segment(id)}/members/${segment(memberID)}`, { query: { expectedRevision }, ...signalOf(options) }),
|
|
400
|
+
assignments: (id, filter = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/assignments`, { query: { memberID: filter.memberID, status: filter.status, limit: filter.limit, cursor: filter.cursor }, ...signalOf(filter) }),
|
|
401
|
+
assignment: (id, assignmentID, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/assignments/${segment(assignmentID)}`, signalOf(options)),
|
|
402
|
+
assign: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/assignments`, { body: input, ...idempotent(options) }),
|
|
403
|
+
setAssignmentStatus: (id, assignmentID, status, expectedRevision, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}/assignments/${segment(assignmentID)}`, { body: { status, expectedRevision }, ...signalOf(options) }),
|
|
404
|
+
reassign: (id, assignmentID, memberID, expectedRevision, options = {}) => this.call("PATCH", `${AGENT}/workspaces/${segment(id)}/assignments/${segment(assignmentID)}`, { body: { memberID, expectedRevision }, ...signalOf(options) }),
|
|
405
|
+
supervision: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/supervision`, signalOf(options)),
|
|
406
|
+
supervise: (id, input, options = {}) => this.call("PUT", `${AGENT}/workspaces/${segment(id)}/supervision`, { body: input, ...signalOf(options) }),
|
|
407
|
+
board: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/board`, signalOf(options)),
|
|
408
|
+
master: (id, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/master`, signalOf(options)),
|
|
409
|
+
replaceMaster: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/master`, { body: input, ...idempotent(options) }),
|
|
410
|
+
createSideChat: (id, input, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/side-chats`, { body: input, ...idempotent(options) }),
|
|
411
|
+
sideChats: (id, filter = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/side-chats`, { query: { limit: filter.limit, cursor: filter.cursor }, ...signalOf(filter) }),
|
|
412
|
+
sideChat: (id, sideChatID, options = {}) => this.call("GET", `${AGENT}/workspaces/${segment(id)}/side-chats/${segment(sideChatID)}`, signalOf(options)),
|
|
413
|
+
sideChatByID: (sideChatID, options = {}) => this.call("GET", `${AGENT}/side-chats/${segment(sideChatID)}`, signalOf(options)),
|
|
414
|
+
refreshSideChat: (id, sideChatID, expectedRevision, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/side-chats/${segment(sideChatID)}/refresh`, { body: { expectedRevision }, ...signalOf(options) }),
|
|
415
|
+
sendToMain: (id, sideChatID, text, options = {}) => this.call("POST", `${AGENT}/workspaces/${segment(id)}/side-chats/${segment(sideChatID)}/send`, { body: { text, confirmed: true }, ...signalOf(options) }),
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** Persistent artifacts (HWA-21): metadata as JSON, bytes inline when small or staged and referenced. */
|
|
419
|
+
readonly artifacts: Artifacts = artifactsApi(this)
|
|
420
|
+
/** Teams (HWA-37): definitions, drafts, staffing and status, plus intake (HWA-36): give work, read it, report status. */
|
|
421
|
+
readonly teams: Teams = teamsApi(this)
|
|
422
|
+
/** Concierge (CON-01): its watchlist (get, update, watch/unwatch, mute/unmute). */
|
|
423
|
+
readonly concierge: Concierge = conciergeApi(this)
|
|
424
|
+
|
|
219
425
|
constructor(options: HubClientOptions) {
|
|
220
426
|
this.url = options.url
|
|
221
427
|
this.#base = options.url.replace(/\/+$/, "")
|
|
@@ -226,14 +432,15 @@ export class HubClient {
|
|
|
226
432
|
/** Sends a request; resolves the response when OK, else throws HubError / HubTransportError. */
|
|
227
433
|
async request(method: string, pathname: string, options: RequestOptions = {}): Promise<Response> {
|
|
228
434
|
const target = `${pathname}${queryString(options.query)}`
|
|
229
|
-
const headers: Record<string, string> = { authorization: `Bearer ${this.#token}`, accept: "application/json" }
|
|
435
|
+
const headers: Record<string, string> = { ...options.headers, authorization: `Bearer ${this.#token}`, accept: "application/json" }
|
|
230
436
|
if (options.body !== undefined) headers["content-type"] = "application/json"
|
|
437
|
+
if (options.raw) headers["content-type"] = options.raw.contentType
|
|
231
438
|
let response: Response
|
|
232
439
|
try {
|
|
233
440
|
response = await this.#fetch(`${this.#base}${target}`, {
|
|
234
441
|
method,
|
|
235
442
|
headers,
|
|
236
|
-
...(options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),
|
|
443
|
+
...(options.raw ? { body: options.raw.body } : options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),
|
|
237
444
|
...(options.signal ? { signal: options.signal } : {}),
|
|
238
445
|
redirect: "manual",
|
|
239
446
|
})
|
|
@@ -282,7 +489,7 @@ export class HubClient {
|
|
|
282
489
|
sessions(filter: SessionsFilter = {}): Promise<{ sessions: SessionView[] }> {
|
|
283
490
|
const state = typeof filter.state === "string" ? filter.state : filter.state?.join(",")
|
|
284
491
|
return this.call("GET", `${AGENT}/sessions`, {
|
|
285
|
-
query: { project: filter.project, state, machine: filter.machine, label: filter.label },
|
|
492
|
+
query: { project: filter.project, state, machine: filter.machine, label: filter.label, q: filter.q },
|
|
286
493
|
...signalOf(filter),
|
|
287
494
|
})
|
|
288
495
|
}
|
|
@@ -298,8 +505,8 @@ export class HubClient {
|
|
|
298
505
|
}
|
|
299
506
|
|
|
300
507
|
/** Named and role-holding sessions. */
|
|
301
|
-
who(options: CallOptions & { readonly project?: string; readonly role?: string } = {}): Promise<
|
|
302
|
-
return this.call("GET", `${AGENT}/who`, { query: { project: options.project, role: options.role === undefined ? undefined : bareRole(options.role) }, ...signalOf(options) })
|
|
508
|
+
who(options: CallOptions & { readonly project?: string; readonly role?: string; readonly team?: string } = {}): Promise<WhoAnswer> {
|
|
509
|
+
return this.call("GET", `${AGENT}/who`, { query: { project: options.project, role: options.role === undefined ? undefined : bareRole(options.role), team: options.team }, ...signalOf(options) })
|
|
303
510
|
}
|
|
304
511
|
|
|
305
512
|
/**
|
|
@@ -355,6 +562,20 @@ export class HubClient {
|
|
|
355
562
|
return this.call("PUT", `${AGENT}/sessions/${segment(addr)}/pin`, { body: { pinned }, ...signalOf(options) })
|
|
356
563
|
}
|
|
357
564
|
|
|
565
|
+
/** (CON-02) A session's intent: the stored one, else its non-placeholder title (source title), else null. */
|
|
566
|
+
intent(addr: string, options: CallOptions = {}): Promise<{ intent: SessionIntent | null }> {
|
|
567
|
+
return this.call("GET", `${AGENT}/sessions/${segment(addr)}/intent`, signalOf(options))
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* (CON-02) Sets (text) or clears (null) the caller's intent layer under expectedRevision (the revision read;
|
|
572
|
+
* stale: conflict with currentRevision). Users and apps (sessions:manage) write source user, a session token its
|
|
573
|
+
* own session only (source self).
|
|
574
|
+
*/
|
|
575
|
+
setIntent(addr: string, text: string | null, expectedRevision: number, options: CallOptions = {}): Promise<{ intent: SessionIntent | null }> {
|
|
576
|
+
return this.call("PUT", `${AGENT}/sessions/${segment(addr)}/intent`, { body: { text, expectedRevision }, ...signalOf(options) })
|
|
577
|
+
}
|
|
578
|
+
|
|
358
579
|
previewArchiveInactive(inactiveFor: string, limit: number, options: CallOptions = {}): Promise<ArchiveInactivePreview> {
|
|
359
580
|
return this.call("POST", `${AGENT}/sessions/archive-inactive`, { body: { preview: true, inactiveFor, limit }, ...signalOf(options) })
|
|
360
581
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// hub.concierge (CON-01) over /api/agent/concierge: the Concierge's watchlist — which of the user's sessions it
|
|
2
|
+
// watches (auto-active sessions, the console's pins, explicit sessions, projects and launch-label filters, minus
|
|
3
|
+
// mutes). Who: users; app tokens with sessions:read (get) or sessions:manage (update and the verbs); the user's current
|
|
4
|
+
// Concierge session through its act token (get and the verbs only). Every other session token gets 404 (not_found).
|
|
5
|
+
// `update` sends the revision you read (expectedRevision; 409 conflict with currentRevision when stale); the verbs are
|
|
6
|
+
// idempotent (`changed: false` when nothing changed) and resolve the address for the caller (a subagent watches its
|
|
7
|
+
// root). The wire types mirror the hub's protocol (packages/protocol/src/concierge.ts); the SDK imports nothing
|
|
8
|
+
// outside its package.
|
|
9
|
+
|
|
10
|
+
interface Signal {
|
|
11
|
+
readonly signal?: AbortSignal
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WatchlistConfig {
|
|
15
|
+
readonly autoActive: boolean
|
|
16
|
+
/** 1–24. */
|
|
17
|
+
readonly activeWithinHours: number
|
|
18
|
+
readonly usePins: boolean
|
|
19
|
+
/** Explicit ses_… IDs (≤ 200). */
|
|
20
|
+
readonly sessions: readonly string[]
|
|
21
|
+
/** Canonical absolute project directories (≤ 50). */
|
|
22
|
+
readonly projects: readonly string[]
|
|
23
|
+
/** ≤ 20 filters; each matches when all its pairs equal a launch's labels. */
|
|
24
|
+
readonly labels: readonly Readonly<Record<string, string>>[]
|
|
25
|
+
/** ses_… IDs (≤ 500); beats every include. */
|
|
26
|
+
readonly muted: readonly string[]
|
|
27
|
+
readonly mutedProjects: readonly string[]
|
|
28
|
+
readonly revision: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type WatchReason = "active" | "pinned" | "pinned_project" | "explicit" | "project" | "label"
|
|
32
|
+
|
|
33
|
+
export interface WatchlistEntry {
|
|
34
|
+
readonly sessionID: string
|
|
35
|
+
readonly reasons: readonly WatchReason[]
|
|
36
|
+
readonly lastActiveAt?: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WatchlistView {
|
|
40
|
+
readonly config: WatchlistConfig
|
|
41
|
+
readonly entries: readonly WatchlistEntry[]
|
|
42
|
+
readonly truncated: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** What PUT takes: any of the config fields (revision is the server's). */
|
|
46
|
+
export type WatchlistUpdate = Partial<Omit<WatchlistConfig, "revision">>
|
|
47
|
+
|
|
48
|
+
/** The answer of the watch/unwatch/mute/unmute verbs: the stored config, the resolved root session, and whether it changed. */
|
|
49
|
+
export interface WatchlistChanged {
|
|
50
|
+
readonly config: WatchlistConfig
|
|
51
|
+
readonly sessionID: string
|
|
52
|
+
readonly changed: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ConciergeWatchlistApi {
|
|
56
|
+
get(options?: Signal): Promise<WatchlistView>
|
|
57
|
+
update(patch: WatchlistUpdate, expectedRevision: number, options?: Signal): Promise<{ config: WatchlistConfig }>
|
|
58
|
+
watch(addr: string, options?: Signal): Promise<WatchlistChanged>
|
|
59
|
+
unwatch(addr: string, options?: Signal): Promise<WatchlistChanged>
|
|
60
|
+
mute(addr: string, options?: Signal): Promise<WatchlistChanged>
|
|
61
|
+
unmute(addr: string, options?: Signal): Promise<WatchlistChanged>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface Concierge {
|
|
65
|
+
readonly watchlist: ConciergeWatchlistApi
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** What hub.concierge needs from the client (HubClient's call). */
|
|
69
|
+
export interface ConciergeTransport {
|
|
70
|
+
call<T>(method: string, pathname: string, options?: { readonly body?: unknown; readonly signal?: AbortSignal }): Promise<T>
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const WATCHLIST = "/api/agent/concierge/watchlist"
|
|
74
|
+
const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
|
|
75
|
+
|
|
76
|
+
export function conciergeApi(hub: ConciergeTransport): Concierge {
|
|
77
|
+
const verb = (name: string) => (addr: string, options?: Signal) => hub.call<WatchlistChanged>("POST", `${WATCHLIST}/${name}`, { body: { session: addr }, ...sig(options) })
|
|
78
|
+
return {
|
|
79
|
+
watchlist: {
|
|
80
|
+
get: (options) => hub.call("GET", WATCHLIST, sig(options)),
|
|
81
|
+
update: (patch, expectedRevision, options) => hub.call("PUT", WATCHLIST, { body: { ...patch, expectedRevision }, ...sig(options) }),
|
|
82
|
+
watch: verb("watch"),
|
|
83
|
+
unwatch: verb("unwatch"),
|
|
84
|
+
mute: verb("mute"),
|
|
85
|
+
unmute: verb("unmute"),
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -14,6 +14,7 @@ export type ErrorCode =
|
|
|
14
14
|
| "not_found"
|
|
15
15
|
| "ambiguous"
|
|
16
16
|
| "name_taken"
|
|
17
|
+
| "conflict"
|
|
17
18
|
| "gone"
|
|
18
19
|
| "unsupported"
|
|
19
20
|
| "paused"
|
|
@@ -28,7 +29,8 @@ export interface Envelope {
|
|
|
28
29
|
readonly hint?: string
|
|
29
30
|
/**
|
|
30
31
|
* More fields, by code: candidates (ambiguous), retryAfter (rate_limited, seconds), feature/harness (unsupported),
|
|
31
|
-
* evaluation/decisionID (needs_user; see needsUserOf)
|
|
32
|
+
* evaluation/decisionID (needs_user; see needsUserOf),
|
|
33
|
+
* currentRevision (conflict).
|
|
32
34
|
*/
|
|
33
35
|
readonly [extra: string]: unknown
|
|
34
36
|
}
|
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
// errors (HubError, HubTransportError), the answer types, the SSE parser of the watch stream, and the pure
|
|
3
3
|
// envelope renderer/sanitiser and address parser shared with the hub, launches, and webhook verification.
|
|
4
4
|
export * from "./address.ts"
|
|
5
|
+
export * from "./artifacts.ts"
|
|
6
|
+
export * from "./artifact-jobs.ts"
|
|
7
|
+
export * from "./team-intake.ts"
|
|
8
|
+
export * from "./teams.ts"
|
|
5
9
|
export * from "./client.ts"
|
|
10
|
+
export * from "./concierge.ts"
|
|
6
11
|
export * from "./envelope.ts"
|
|
7
12
|
export * from "./errors.ts"
|
|
8
13
|
export * from "./sse.ts"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// hub.teams intake verbs (HWA-36) over /api/agent/teams/:id/intake: give work to a team (its entry role receives it
|
|
2
|
+
// as a hub message and owns it as a workspace assignment), read it back, and report status explicitly. Users; app
|
|
3
|
+
// tokens with teams:submit (submit, read and cancel their own submissions); session tokens (submit under team policy;
|
|
4
|
+
// sessions in the team's entry role report progress). Definitions, staffing and the rest of hub.teams are HWA-37's.
|
|
5
|
+
// The wire type mirrors the hub's StoredIntake (packages/protocol TeamIntake plus additive fields; the SDK imports
|
|
6
|
+
// nothing outside its package; the hub's tests keep them in step).
|
|
7
|
+
|
|
8
|
+
interface Signal {
|
|
9
|
+
readonly signal?: AbortSignal
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type TeamIntakeStatus = "queued" | "routed" | "done" | "cancelled" | "failed"
|
|
13
|
+
export type TeamIntakePriority = "low" | "normal" | "high" | "urgent"
|
|
14
|
+
export type TeamIntakeProgress = "open" | "in_progress" | "blocked"
|
|
15
|
+
/** What setIntakeStatus accepts: progress while routed, or a closing status (submitters: cancelled only). */
|
|
16
|
+
export type TeamIntakeStatusInput = TeamIntakeProgress | "done" | "failed" | "cancelled"
|
|
17
|
+
|
|
18
|
+
export interface TeamIntake {
|
|
19
|
+
readonly v: 1
|
|
20
|
+
readonly id: string
|
|
21
|
+
readonly teamID: string
|
|
22
|
+
readonly title: string
|
|
23
|
+
readonly source: { readonly kind: "user" } | { readonly kind: "app"; readonly tokenID: string } | { readonly kind: "session"; readonly sessionID: string }
|
|
24
|
+
readonly ref?: string
|
|
25
|
+
readonly assignmentID?: string
|
|
26
|
+
readonly status: TeamIntakeStatus
|
|
27
|
+
readonly revision: number
|
|
28
|
+
readonly createdAt: number
|
|
29
|
+
readonly priority: TeamIntakePriority
|
|
30
|
+
readonly labels?: readonly string[]
|
|
31
|
+
/** Epoch ms. */
|
|
32
|
+
readonly due?: number
|
|
33
|
+
readonly criteria?: readonly string[]
|
|
34
|
+
readonly workspaceID?: string
|
|
35
|
+
readonly sessionID?: string
|
|
36
|
+
readonly messageID?: string
|
|
37
|
+
readonly progress?: TeamIntakeProgress
|
|
38
|
+
readonly reason?: string
|
|
39
|
+
readonly lastError?: string
|
|
40
|
+
readonly updatedAt: number
|
|
41
|
+
readonly routedAt?: number
|
|
42
|
+
readonly closedAt?: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TeamIntakeSubmit {
|
|
46
|
+
readonly title: string
|
|
47
|
+
/** Delivered in the message only; never stored after delivery, never in events. */
|
|
48
|
+
readonly brief?: string
|
|
49
|
+
readonly priority?: TeamIntakePriority
|
|
50
|
+
readonly labels?: readonly string[]
|
|
51
|
+
/** Epoch ms. */
|
|
52
|
+
readonly due?: number
|
|
53
|
+
readonly criteria?: readonly string[]
|
|
54
|
+
/** An external reference (e.g. a Brain task ID). */
|
|
55
|
+
readonly ref?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The assignment a status change moved (as hub.workspaces shows it). */
|
|
59
|
+
export interface TeamIntakeAssignment {
|
|
60
|
+
readonly id: string
|
|
61
|
+
readonly workspaceID: string
|
|
62
|
+
readonly memberID: string
|
|
63
|
+
readonly status: "open" | "in_progress" | "blocked" | "done" | "cancelled"
|
|
64
|
+
readonly revision: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface TeamIntakeApi {
|
|
68
|
+
/** Submits work; `created` false for an Idempotency-Key replay. Queued while the entry role is vacant. */
|
|
69
|
+
submit(team: string, input: TeamIntakeSubmit, options?: Signal & { readonly idempotencyKey?: string }): Promise<{ intake: TeamIntake; created: boolean }>
|
|
70
|
+
intake(team: string, intakeID: string, options?: Signal): Promise<{ intake: TeamIntake }>
|
|
71
|
+
intakes(team: string, filter?: Signal & { readonly status?: TeamIntakeStatus; readonly limit?: number; readonly cursor?: string }): Promise<{ items: readonly TeamIntake[]; cursor?: string }>
|
|
72
|
+
setIntakeStatus(
|
|
73
|
+
team: string,
|
|
74
|
+
intakeID: string,
|
|
75
|
+
input: { readonly status: TeamIntakeStatusInput; readonly expectedRevision: number; readonly reason?: string },
|
|
76
|
+
options?: Signal,
|
|
77
|
+
): Promise<{ intake: TeamIntake; assignment?: TeamIntakeAssignment }>
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
type Query = Readonly<Record<string, string | number | undefined>>
|
|
81
|
+
|
|
82
|
+
/** What hub.teams needs from the client (HubClient's request and call). */
|
|
83
|
+
export interface TeamIntakeTransport {
|
|
84
|
+
request(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly headers?: Readonly<Record<string, string>>; readonly signal?: AbortSignal }): Promise<Response>
|
|
85
|
+
call<T>(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly signal?: AbortSignal }): Promise<T>
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const seg = (value: string) => encodeURIComponent(value)
|
|
89
|
+
const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
|
|
90
|
+
const path = (team: string) => `/api/agent/teams/${seg(team)}/intake`
|
|
91
|
+
|
|
92
|
+
export function teamIntakeApi(hub: TeamIntakeTransport): TeamIntakeApi {
|
|
93
|
+
return {
|
|
94
|
+
submit: async (team, input, options = {}) => {
|
|
95
|
+
const response = await hub.request("POST", path(team), { body: input, ...(options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {}), ...sig(options) })
|
|
96
|
+
const answer = (await response.json()) as { intake: TeamIntake }
|
|
97
|
+
return { intake: answer.intake, created: response.status === 201 }
|
|
98
|
+
},
|
|
99
|
+
intake: (team, intakeID, options) => hub.call("GET", `${path(team)}/${seg(intakeID)}`, sig(options)),
|
|
100
|
+
intakes: (team, filter = {}) => hub.call("GET", path(team), { query: { status: filter.status, limit: filter.limit, cursor: filter.cursor }, ...sig(filter) }),
|
|
101
|
+
setIntakeStatus: (team, intakeID, input, options) => hub.call("POST", `${path(team)}/${seg(intakeID)}/status`, { body: input, ...sig(options) }),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Every status setIntakeStatus accepts, in the order tools list them. */
|
|
106
|
+
export const TEAM_INTAKE_STATUS_INPUTS: readonly TeamIntakeStatusInput[] = ["open", "in_progress", "blocked", "done", "failed", "cancelled"]
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Reports an intake item's status (the entry role's progress or closing, a submitter's cancel). Without
|
|
110
|
+
* `expectedRevision` the item is read first and its current revision sent: a change in between is still a conflict.
|
|
111
|
+
* Authority is the hub's (the route's rules); nothing is decided here.
|
|
112
|
+
*/
|
|
113
|
+
export async function reportIntakeStatus(
|
|
114
|
+
api: Pick<TeamIntakeApi, "intake" | "setIntakeStatus">,
|
|
115
|
+
team: string,
|
|
116
|
+
intakeID: string,
|
|
117
|
+
input: { readonly status: TeamIntakeStatusInput; readonly expectedRevision?: number; readonly reason?: string },
|
|
118
|
+
options: Signal = {},
|
|
119
|
+
): Promise<{ intake: TeamIntake; assignment?: TeamIntakeAssignment }> {
|
|
120
|
+
const expectedRevision = input.expectedRevision ?? (await api.intake(team, intakeID, options)).intake.revision
|
|
121
|
+
return api.setIntakeStatus(team, intakeID, { status: input.status, expectedRevision, ...(input.reason !== undefined ? { reason: input.reason } : {}) }, options)
|
|
122
|
+
}
|