@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
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// hub.artifacts.jobs (HWA-24): agent-backed AI jobs on an artifact revision over /api/agent/artifacts/:id/jobs.
|
|
2
|
+
// A job runs with the requester's own authority: its handler is an ordinary session the requester launches
|
|
3
|
+
// (handler.launch, through the launch API: scope sessions:launch and a launch grant for apps and sessions) or
|
|
4
|
+
// messages (handler.session). The handler reads a bounded, untrusted copy of the revision (context) and submits
|
|
5
|
+
// one result; a propose-edit result is a proposal the user (or the requesting app, with artifacts:write) applies
|
|
6
|
+
// as a CAS on the job's base revision. Needs scope artifacts:ai (+ artifacts:read) for an app token.
|
|
7
|
+
// The wire types mirror src/central/artifacts/jobs.ts (the SDK imports nothing outside its package).
|
|
8
|
+
import type { ArtifactPage, ArtifactRevision, ArtifactTransport, InlineFile, KeyOptions } from "./artifacts.ts"
|
|
9
|
+
|
|
10
|
+
interface Signal {
|
|
11
|
+
readonly signal?: AbortSignal
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ArtifactJobKind = "ask" | "propose-edit" | "run-action"
|
|
15
|
+
export type ArtifactJobState = "queued" | "running" | "succeeded" | "failed" | "cancelled"
|
|
16
|
+
export type ArtifactJobHandlerStatus = "starting" | "waiting" | "offline" | "ready" | "ended" | "failed"
|
|
17
|
+
|
|
18
|
+
export interface ArtifactJobLaunchHandler {
|
|
19
|
+
/** The machine (ID or name) that runs the handler session; placement is required there. */
|
|
20
|
+
readonly machine: string
|
|
21
|
+
/** An absolute repository path on that machine. */
|
|
22
|
+
readonly repo: string
|
|
23
|
+
readonly harness?: string
|
|
24
|
+
readonly model?: string
|
|
25
|
+
readonly agent?: string
|
|
26
|
+
readonly mode?: "checkout" | "worktree"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type ArtifactJobHandlerInput = { readonly launch: ArtifactJobLaunchHandler } | { readonly session: string }
|
|
30
|
+
|
|
31
|
+
export interface ArtifactJobRequest {
|
|
32
|
+
/** Default propose-edit. */
|
|
33
|
+
readonly kind?: ArtifactJobKind
|
|
34
|
+
readonly baseRevisionID: string
|
|
35
|
+
readonly instruction: string
|
|
36
|
+
/** run-action: one of the artifact's declared actions (metadata `ai-actions`). */
|
|
37
|
+
readonly action?: string
|
|
38
|
+
readonly handler: ArtifactJobHandlerInput
|
|
39
|
+
/** propose-edit by a user: "auto" applies the result at once (still a CAS on the base). */
|
|
40
|
+
readonly apply?: "manual" | "auto"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ArtifactJobHandler =
|
|
44
|
+
| {
|
|
45
|
+
readonly kind: "launch"
|
|
46
|
+
readonly status: ArtifactJobHandlerStatus
|
|
47
|
+
readonly machine: string
|
|
48
|
+
readonly repo: string
|
|
49
|
+
readonly harness: string
|
|
50
|
+
readonly mode: "checkout" | "worktree"
|
|
51
|
+
readonly model?: string
|
|
52
|
+
readonly agent?: string
|
|
53
|
+
readonly launchID?: string
|
|
54
|
+
readonly launchState?: string
|
|
55
|
+
readonly reason?: string
|
|
56
|
+
readonly outcome?: string
|
|
57
|
+
readonly sessionID?: string
|
|
58
|
+
}
|
|
59
|
+
| {
|
|
60
|
+
readonly kind: "session"
|
|
61
|
+
readonly status: ArtifactJobHandlerStatus
|
|
62
|
+
readonly address: string
|
|
63
|
+
readonly sessionID?: string
|
|
64
|
+
readonly messageID?: string
|
|
65
|
+
readonly delivery?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ArtifactJobProposal {
|
|
69
|
+
readonly state: "pending" | "applied" | "conflict"
|
|
70
|
+
readonly files: readonly { readonly path?: string; readonly mediaType: string; readonly bytes: number; readonly hash: string }[]
|
|
71
|
+
readonly keep?: readonly string[]
|
|
72
|
+
readonly entry?: string
|
|
73
|
+
readonly appliedRevisionID?: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ArtifactJob {
|
|
77
|
+
readonly v: 1
|
|
78
|
+
readonly id: string
|
|
79
|
+
readonly revision: number
|
|
80
|
+
readonly artifactID: string
|
|
81
|
+
readonly baseRevisionID: string
|
|
82
|
+
readonly requestedBy: { readonly kind: "user" } | { readonly kind: "session"; readonly sessionID: string }
|
|
83
|
+
readonly state: ArtifactJobState
|
|
84
|
+
readonly launchID?: string
|
|
85
|
+
readonly sessionID?: string
|
|
86
|
+
readonly resultRevisionID?: string
|
|
87
|
+
readonly error?: string
|
|
88
|
+
readonly createdAt: number
|
|
89
|
+
readonly updatedAt: number
|
|
90
|
+
readonly kind: ArtifactJobKind
|
|
91
|
+
readonly action?: string
|
|
92
|
+
readonly instruction: string
|
|
93
|
+
/** `user`, `app:<token id>` or the requesting session's ID. */
|
|
94
|
+
readonly by: string
|
|
95
|
+
readonly handler: ArtifactJobHandler
|
|
96
|
+
readonly apply: "manual" | "auto"
|
|
97
|
+
readonly deadline: number
|
|
98
|
+
readonly startedAt?: number
|
|
99
|
+
readonly finishedAt?: number
|
|
100
|
+
readonly answer?: string
|
|
101
|
+
readonly proposal?: ArtifactJobProposal
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The bounded copy of the job's revision its handler reads. The content is untrusted data. */
|
|
105
|
+
export interface ArtifactJobContext {
|
|
106
|
+
readonly untrusted: true
|
|
107
|
+
readonly notice: string
|
|
108
|
+
readonly job: { readonly id: string; readonly kind: ArtifactJobKind; readonly action?: string; readonly instruction: string; readonly state: ArtifactJobState }
|
|
109
|
+
readonly artifact: { readonly id: string; readonly kind: string; readonly title: string }
|
|
110
|
+
readonly revision: { readonly id: string; readonly number: number; readonly entry?: string }
|
|
111
|
+
readonly files: readonly {
|
|
112
|
+
readonly path?: string
|
|
113
|
+
readonly mediaType: string
|
|
114
|
+
readonly bytes: number
|
|
115
|
+
readonly hash: string
|
|
116
|
+
readonly encoding?: "utf8"
|
|
117
|
+
readonly text?: string
|
|
118
|
+
/** Not copied: a binary file, or past the context budget. */
|
|
119
|
+
readonly omitted?: "binary" | "budget"
|
|
120
|
+
}[]
|
|
121
|
+
readonly truncated: boolean
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** A propose-edit job's candidate files for review before apply (users and the requesting app). Untrusted data. */
|
|
125
|
+
export interface ArtifactJobProposalView {
|
|
126
|
+
readonly untrusted: true
|
|
127
|
+
readonly notice: string
|
|
128
|
+
readonly state: ArtifactJobProposal["state"]
|
|
129
|
+
readonly entry?: string
|
|
130
|
+
/** Base-revision paths carried over unchanged. */
|
|
131
|
+
readonly keep: readonly string[]
|
|
132
|
+
readonly files: readonly {
|
|
133
|
+
readonly path?: string
|
|
134
|
+
readonly mediaType: string
|
|
135
|
+
readonly bytes: number
|
|
136
|
+
readonly hash: string
|
|
137
|
+
readonly encoding?: "utf8"
|
|
138
|
+
readonly text?: string
|
|
139
|
+
/** Not copied: a binary file, or past the review budget. */
|
|
140
|
+
readonly omitted?: "binary" | "budget"
|
|
141
|
+
}[]
|
|
142
|
+
readonly truncated: boolean
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** A handler's result: an answer (ask, run-action), a proposal (propose-edit: inline files, kept base paths), or an error. */
|
|
146
|
+
export type ArtifactJobResult =
|
|
147
|
+
| { readonly answer: string }
|
|
148
|
+
| { readonly files?: readonly Omit<InlineFile, "hash">[]; readonly keep?: readonly string[]; readonly entry?: string; readonly answer?: string }
|
|
149
|
+
| { readonly error: string }
|
|
150
|
+
|
|
151
|
+
export interface ArtifactJobCreated {
|
|
152
|
+
readonly job: ArtifactJob
|
|
153
|
+
/** False when an Idempotency-Key repeat answered the original. */
|
|
154
|
+
readonly created: boolean
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** hub.artifacts.jobs. */
|
|
158
|
+
export interface ArtifactJobs {
|
|
159
|
+
create(artifactID: string, input: ArtifactJobRequest, options?: KeyOptions): Promise<ArtifactJobCreated>
|
|
160
|
+
list(artifactID: string, options?: Signal & { readonly limit?: number; readonly cursor?: string }): Promise<ArtifactPage<ArtifactJob>>
|
|
161
|
+
get(artifactID: string, jobID: string, options?: Signal): Promise<{ job: ArtifactJob }>
|
|
162
|
+
/** The handler (or the user): the job's bounded, untrusted revision copy. */
|
|
163
|
+
context(artifactID: string, jobID: string, options?: Signal): Promise<ArtifactJobContext>
|
|
164
|
+
/** Users and the requesting app: a proposal's files to review before apply (reading changes nothing). */
|
|
165
|
+
proposal(artifactID: string, jobID: string, options?: Signal): Promise<ArtifactJobProposalView>
|
|
166
|
+
/** The handler session only; a repeated identical result is tolerated. */
|
|
167
|
+
submit(artifactID: string, jobID: string, result: ArtifactJobResult, options?: Signal): Promise<{ job: ArtifactJob }>
|
|
168
|
+
/** Users (and the requesting app with artifacts:write): publishes the proposal on the job's base (stale: conflict). */
|
|
169
|
+
apply(artifactID: string, jobID: string, expectedRevision: number, options?: Signal): Promise<{ job: ArtifactJob; revision: ArtifactRevision }>
|
|
170
|
+
cancel(artifactID: string, jobID: string, expectedRevision: number, options?: Signal): Promise<{ job: ArtifactJob; handlerStopped: boolean }>
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const PATH = "/api/agent/artifacts"
|
|
174
|
+
const seg = (value: string) => encodeURIComponent(value)
|
|
175
|
+
const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
|
|
176
|
+
|
|
177
|
+
export function artifactJobsApi(hub: ArtifactTransport): ArtifactJobs {
|
|
178
|
+
const jobs = (id: string) => `${PATH}/${seg(id)}/jobs`
|
|
179
|
+
const job = (id: string, jobID: string, tail = "") => `${jobs(id)}/${seg(jobID)}${tail}`
|
|
180
|
+
return {
|
|
181
|
+
create: async (artifactID, input, options = {}) => {
|
|
182
|
+
const response = await hub.request("POST", jobs(artifactID), { body: input, ...(options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {}), ...sig(options) })
|
|
183
|
+
const answer = (await response.json()) as { job: ArtifactJob }
|
|
184
|
+
return { job: answer.job, created: response.status === 201 }
|
|
185
|
+
},
|
|
186
|
+
list: (artifactID, options = {}) => hub.call("GET", jobs(artifactID), { query: { limit: options.limit, cursor: options.cursor }, ...sig(options) }),
|
|
187
|
+
get: (artifactID, jobID, options) => hub.call("GET", job(artifactID, jobID), sig(options)),
|
|
188
|
+
context: (artifactID, jobID, options) => hub.call("GET", job(artifactID, jobID, "/context"), sig(options)),
|
|
189
|
+
proposal: (artifactID, jobID, options) => hub.call("GET", job(artifactID, jobID, "/proposal"), sig(options)),
|
|
190
|
+
submit: (artifactID, jobID, result, options) => hub.call("POST", job(artifactID, jobID, "/result"), { body: result, ...sig(options) }),
|
|
191
|
+
apply: (artifactID, jobID, expectedRevision, options) => hub.call("POST", job(artifactID, jobID, "/apply"), { body: { expectedRevision }, ...sig(options) }),
|
|
192
|
+
cancel: (artifactID, jobID, expectedRevision, options) => hub.call("POST", job(artifactID, jobID, "/cancel"), { body: { expectedRevision }, ...sig(options) }),
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// hub.artifacts (HWA-21): persistent artifacts over /api/agent/artifacts. Metadata travels as JSON; bytes go two
|
|
2
|
+
// ways so nobody embeds huge payloads: small files inline in a create/revise (`upload`, ≤ 1 MiB decoded per
|
|
3
|
+
// request), anything bigger staged first with uploadBlob (a raw streamed body, ≤ 25 MiB) and then referenced by its
|
|
4
|
+
// BlobRef in `blobs`. Downloads answer the raw Response (stream it; it is an attachment, never rendered).
|
|
5
|
+
// The wire types mirror packages/protocol/src/artifacts.ts (the SDK imports nothing outside its package; the hub's
|
|
6
|
+
// tests keep them in step).
|
|
7
|
+
|
|
8
|
+
import { type ArtifactJobs, artifactJobsApi } from "./artifact-jobs.ts"
|
|
9
|
+
|
|
10
|
+
/** Only an abort signal (as the client's CallOptions). */
|
|
11
|
+
interface Signal {
|
|
12
|
+
readonly signal?: AbortSignal
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ArtifactKind = "markdown" | "code" | "image" | "table" | "chart" | "dashboard" | "html" | "app" | "file"
|
|
16
|
+
export type ArtifactVisibility = "private" | "workspace"
|
|
17
|
+
export type ArtifactState = "active" | "archived"
|
|
18
|
+
|
|
19
|
+
export interface Artifact {
|
|
20
|
+
readonly v: 1
|
|
21
|
+
readonly id: string
|
|
22
|
+
readonly revision: number
|
|
23
|
+
readonly title: string
|
|
24
|
+
readonly description?: string
|
|
25
|
+
readonly kind: ArtifactKind
|
|
26
|
+
readonly state: ArtifactState
|
|
27
|
+
readonly visibility: ArtifactVisibility
|
|
28
|
+
readonly workspaceID?: string
|
|
29
|
+
readonly createdBySessionID?: string
|
|
30
|
+
readonly latestRevisionID: string
|
|
31
|
+
readonly tags?: readonly string[]
|
|
32
|
+
readonly metadata?: Readonly<Record<string, string>>
|
|
33
|
+
readonly createdAt: number
|
|
34
|
+
readonly updatedAt: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface BlobRef {
|
|
38
|
+
/** `sha256:<64 hex>`. */
|
|
39
|
+
readonly hash: string
|
|
40
|
+
readonly bytes: number
|
|
41
|
+
readonly mediaType: string
|
|
42
|
+
readonly path?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ArtifactRevision {
|
|
46
|
+
readonly v: 1
|
|
47
|
+
readonly id: string
|
|
48
|
+
readonly artifactID: string
|
|
49
|
+
readonly number: number
|
|
50
|
+
readonly baseRevisionID?: string
|
|
51
|
+
readonly blobs: readonly BlobRef[]
|
|
52
|
+
readonly entry?: string
|
|
53
|
+
readonly author: { readonly kind: "user" } | { readonly kind: "session"; readonly sessionID: string } | { readonly kind: "ai_job"; readonly jobID: string }
|
|
54
|
+
readonly createdAt: number
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** A small file sent inline (utf8 text, or base64 bytes). */
|
|
58
|
+
export interface InlineFile {
|
|
59
|
+
readonly mediaType: string
|
|
60
|
+
readonly data: string
|
|
61
|
+
readonly encoding?: "utf8" | "base64"
|
|
62
|
+
readonly path?: string
|
|
63
|
+
/** `sha256:<hex>` the decoded bytes must hash to. */
|
|
64
|
+
readonly hash?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The files of a revision: references to stored bytes and/or inline files; `entry` names a bundle's entry path. */
|
|
68
|
+
export interface RevisionFiles {
|
|
69
|
+
readonly blobs?: readonly BlobRef[]
|
|
70
|
+
readonly upload?: readonly InlineFile[]
|
|
71
|
+
readonly entry?: string
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ArtifactCreate extends RevisionFiles {
|
|
75
|
+
readonly title: string
|
|
76
|
+
readonly kind: ArtifactKind
|
|
77
|
+
readonly description?: string
|
|
78
|
+
readonly visibility?: ArtifactVisibility
|
|
79
|
+
readonly workspaceID?: string
|
|
80
|
+
readonly tags?: readonly string[]
|
|
81
|
+
readonly metadata?: Readonly<Record<string, string>>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ArtifactPage<T> {
|
|
85
|
+
readonly items: readonly T[]
|
|
86
|
+
readonly cursor?: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Published {
|
|
90
|
+
readonly artifact: Artifact
|
|
91
|
+
readonly revision: ArtifactRevision
|
|
92
|
+
/** False when an Idempotency-Key repeat answered the original. */
|
|
93
|
+
readonly created: boolean
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface StagedBlob {
|
|
97
|
+
readonly blob: BlobRef
|
|
98
|
+
/** Reference it in a create/revise within this time, or recovery may collect the bytes. */
|
|
99
|
+
readonly referenceWithinMs: number
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ArtifactsFilter extends Signal {
|
|
103
|
+
readonly limit?: number
|
|
104
|
+
readonly cursor?: string
|
|
105
|
+
readonly state?: ArtifactState
|
|
106
|
+
readonly workspace?: string
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface KeyOptions extends Signal {
|
|
110
|
+
/** Idempotency-Key: the same key and body again answer the original (created false). */
|
|
111
|
+
readonly idempotencyKey?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface ArtifactUpdate {
|
|
115
|
+
readonly expectedRevision: number
|
|
116
|
+
readonly title?: string
|
|
117
|
+
readonly description?: string
|
|
118
|
+
readonly tags?: readonly string[]
|
|
119
|
+
readonly metadata?: Readonly<Record<string, string>>
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** hub.artifacts (users; app tokens with artifacts:read / artifacts:write; sessions read workspace-shared ones and create). */
|
|
123
|
+
export interface Artifacts {
|
|
124
|
+
list(filter?: ArtifactsFilter): Promise<ArtifactPage<Artifact>>
|
|
125
|
+
get(id: string, options?: Signal): Promise<{ artifact: Artifact }>
|
|
126
|
+
create(input: ArtifactCreate, options?: KeyOptions): Promise<Published>
|
|
127
|
+
/** A new revision on `baseRevisionID` (the latest you read; a stale one is a conflict HubError). */
|
|
128
|
+
revise(id: string, input: RevisionFiles & { readonly baseRevisionID: string }, options?: KeyOptions): Promise<Published>
|
|
129
|
+
revisions(id: string, options?: Signal & { readonly limit?: number; readonly cursor?: string }): Promise<ArtifactPage<ArtifactRevision>>
|
|
130
|
+
revision(id: string, revisionID: string, options?: Signal): Promise<{ revision: ArtifactRevision }>
|
|
131
|
+
/** Stages raw bytes (streamed) under a declared media type; answers the reference to use in `blobs`. */
|
|
132
|
+
uploadBlob(body: BodyInit, mediaType: string, options?: Signal & { readonly hash?: string }): Promise<StagedBlob>
|
|
133
|
+
/** The bytes of one blob of a revision, as the raw Response (an attachment). */
|
|
134
|
+
download(id: string, revisionID: string, hash: string, options?: Signal): Promise<Response>
|
|
135
|
+
update(id: string, input: ArtifactUpdate, options?: Signal): Promise<{ artifact: Artifact }>
|
|
136
|
+
setVisibility(id: string, input: { readonly expectedRevision: number; readonly visibility: ArtifactVisibility; readonly workspaceID?: string }, options?: Signal): Promise<{ artifact: Artifact }>
|
|
137
|
+
archive(id: string, expectedRevision: number, options?: Signal): Promise<{ artifact: Artifact }>
|
|
138
|
+
unarchive(id: string, expectedRevision: number, options?: Signal): Promise<{ artifact: Artifact }>
|
|
139
|
+
delete(id: string, expectedRevision: number, options?: Signal): Promise<void>
|
|
140
|
+
/** AI jobs on an artifact revision (HWA-24; scope artifacts:ai). */
|
|
141
|
+
readonly jobs: ArtifactJobs
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
type Query = Readonly<Record<string, string | number | undefined>>
|
|
145
|
+
|
|
146
|
+
/** What hub.artifacts needs from the client (HubClient's request and call). */
|
|
147
|
+
export interface ArtifactTransport {
|
|
148
|
+
request(
|
|
149
|
+
method: string,
|
|
150
|
+
pathname: string,
|
|
151
|
+
options?: { readonly query?: Query; readonly body?: unknown; readonly raw?: { readonly body: BodyInit; readonly contentType: string }; readonly headers?: Readonly<Record<string, string>>; readonly signal?: AbortSignal },
|
|
152
|
+
): Promise<Response>
|
|
153
|
+
call<T>(method: string, pathname: string, options?: { readonly query?: Query; readonly body?: unknown; readonly signal?: AbortSignal }): Promise<T>
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const PATH = "/api/agent/artifacts"
|
|
157
|
+
const seg = (value: string) => encodeURIComponent(value)
|
|
158
|
+
const sig = (options: Signal = {}) => (options.signal ? { signal: options.signal } : {})
|
|
159
|
+
|
|
160
|
+
export function artifactsApi(hub: ArtifactTransport): Artifacts {
|
|
161
|
+
const publish = async (pathname: string, body: unknown, options: KeyOptions = {}): Promise<Published> => {
|
|
162
|
+
const response = await hub.request("POST", pathname, { body, ...(options.idempotencyKey ? { headers: { "idempotency-key": options.idempotencyKey } } : {}), ...sig(options) })
|
|
163
|
+
const answer = (await response.json()) as { artifact: Artifact; revision: ArtifactRevision }
|
|
164
|
+
return { artifact: answer.artifact, revision: answer.revision, created: response.status === 201 }
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
list: (filter = {}) => hub.call("GET", PATH, { query: { limit: filter.limit, cursor: filter.cursor, state: filter.state, workspace: filter.workspace }, ...sig(filter) }),
|
|
168
|
+
get: (id, options) => hub.call("GET", `${PATH}/${seg(id)}`, sig(options)),
|
|
169
|
+
create: (input, options) => publish(PATH, input, options),
|
|
170
|
+
revise: (id, input, options) => publish(`${PATH}/${seg(id)}/revisions`, input, options),
|
|
171
|
+
revisions: (id, options = {}) => hub.call("GET", `${PATH}/${seg(id)}/revisions`, { query: { limit: options.limit, cursor: options.cursor }, ...sig(options) }),
|
|
172
|
+
revision: (id, revisionID, options) => hub.call("GET", `${PATH}/${seg(id)}/revisions/${seg(revisionID)}`, sig(options)),
|
|
173
|
+
uploadBlob: async (body, mediaType, options = {}) => {
|
|
174
|
+
const response = await hub.request("POST", `${PATH}/blobs`, { raw: { body, contentType: mediaType }, ...(options.hash ? { headers: { "x-blob-hash": options.hash } } : {}), ...sig(options) })
|
|
175
|
+
return (await response.json()) as StagedBlob
|
|
176
|
+
},
|
|
177
|
+
download: (id, revisionID, hash, options) => hub.request("GET", `${PATH}/${seg(id)}/revisions/${seg(revisionID)}/blobs/${seg(hash)}`, sig(options)),
|
|
178
|
+
update: (id, input, options) => hub.call("PATCH", `${PATH}/${seg(id)}`, { body: input, ...sig(options) }),
|
|
179
|
+
setVisibility: (id, input, options) => hub.call("PUT", `${PATH}/${seg(id)}/visibility`, { body: input, ...sig(options) }),
|
|
180
|
+
archive: (id, expectedRevision, options) => hub.call("POST", `${PATH}/${seg(id)}/archive`, { body: { expectedRevision }, ...sig(options) }),
|
|
181
|
+
unarchive: (id, expectedRevision, options) => hub.call("POST", `${PATH}/${seg(id)}/unarchive`, { body: { expectedRevision }, ...sig(options) }),
|
|
182
|
+
delete: async (id, expectedRevision, options) => {
|
|
183
|
+
await hub.call("DELETE", `${PATH}/${seg(id)}`, { body: { expectedRevision }, ...sig(options) })
|
|
184
|
+
},
|
|
185
|
+
jobs: artifactJobsApi(hub),
|
|
186
|
+
}
|
|
187
|
+
}
|