@huynle/hub 0.7.4 → 0.8.1
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 +430 -1
- package/Dockerfile +7 -2
- package/README.md +230 -32
- 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/paths.ts +3 -3
- 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 +6871 -3460
- 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/adapters/node-services/render.ts +2 -2
- package/packages/cli/src/bootstrap/discovery/host.ts +15 -1
- package/packages/cli/src/bootstrap/discovery/probe.ts +1 -1
- package/packages/cli/src/bootstrap/enrollment/enroll.ts +21 -9
- package/packages/cli/src/bootstrap/enrollment/policy.ts +138 -12
- package/packages/cli/src/bootstrap/enrollment/status.ts +3 -2
- package/packages/cli/src/cli.ts +96 -14
- package/packages/cli/src/format.ts +48 -10
- package/packages/cli/src/http.ts +2 -0
- package/packages/cli/src/intents.ts +23 -0
- package/packages/cli/src/launches.ts +9 -3
- package/packages/cli/src/login.ts +20 -6
- package/packages/cli/src/mcp.ts +385 -10
- package/packages/cli/src/messaging.ts +7 -3
- package/packages/cli/src/profiles-mcp.ts +301 -0
- package/packages/cli/src/setup/commands.ts +23 -4
- package/packages/cli/src/setup/runtime.ts +3 -2
- package/packages/cli/src/spoke-services.ts +1 -1
- package/packages/cli/src/team-intake-mcp.ts +158 -0
- package/packages/cli/src/teams-mcp.ts +460 -0
- package/packages/cli/src/watchlist.ts +135 -0
- package/packages/codex/package.json +1 -1
- package/packages/codex/src/paths.ts +3 -3
- 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 +104 -2
- package/packages/node/src/opencode-warm.ts +344 -0
- package/packages/node/src/run.ts +28 -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 +175 -6
- package/packages/protocol/src/launch.ts +36 -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 +4 -1
- package/packages/sdk/src/index.ts +6 -0
- package/packages/sdk/src/team-intake.ts +122 -0
- package/packages/sdk/src/teams.ts +346 -0
- package/packages/sdk/src/types.ts +568 -1
- package/packages/sdk/src/wake.ts +20 -0
- 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 +218 -10
- package/src/central/agent/app-tokens.ts +3 -2
- package/src/central/agent/audit.ts +18 -1
- package/src/central/agent/body.ts +163 -0
- package/src/central/agent/decisions.ts +125 -2
- package/src/central/agent/delivery.ts +194 -40
- package/src/central/agent/directory-routes.ts +37 -7
- package/src/central/agent/directory.ts +70 -14
- package/src/central/agent/errors.ts +7 -0
- package/src/central/agent/escalation.ts +10 -2
- package/src/central/agent/events-routes.ts +21 -4
- package/src/central/agent/events.ts +18 -4
- 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 +189 -32
- package/src/central/agent/launch-routes.ts +26 -10
- package/src/central/agent/launch-spec.ts +40 -3
- package/src/central/agent/launch-store.ts +55 -2
- package/src/central/agent/message-limits.ts +89 -11
- package/src/central/agent/message-routes.ts +10 -4
- package/src/central/agent/messages.ts +28 -2
- package/src/central/agent/names.ts +12 -2
- package/src/central/agent/placement.ts +93 -18
- package/src/central/agent/read-routes.ts +89 -19
- package/src/central/agent/routes.ts +178 -5
- package/src/central/agent/webhook-routes.ts +2 -1
- package/src/central/agent/webhooks.ts +17 -2
- 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 +776 -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 +350 -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 +86 -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 +131 -0
- package/src/central/concierge/proposals.ts +268 -0
- package/src/central/concierge/push-remap.ts +49 -0
- package/src/central/concierge/routes.ts +122 -0
- package/src/central/concierge/schedule.ts +113 -0
- package/src/central/concierge/service.ts +243 -0
- package/src/central/concierge/session-routes.ts +125 -0
- package/src/central/concierge/settings.ts +217 -0
- package/src/central/concierge/supervisor.ts +767 -0
- package/src/central/concierge/tiers.ts +260 -0
- package/src/central/concierge/watchlist-routes.ts +279 -0
- package/src/central/concierge/watchlist.ts +273 -0
- package/src/central/config.ts +28 -6
- package/src/central/http.ts +660 -54
- package/src/central/main.ts +1 -1
- 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 +69 -7
- 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 +786 -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 +910 -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 +126 -23
- package/src/migrate.ts +109 -3
- package/src/spoke/concierge-agent.ts +33 -0
- package/src/spoke/deliver.ts +22 -4
- package/src/spoke/env-inject.ts +8 -1
- package/src/spoke/fork-revert.ts +142 -0
- package/src/spoke/hub-rpc.ts +41 -5
- package/src/spoke/hub-tool.ts +404 -25
- package/src/spoke/launchers.ts +6 -4
- package/src/spoke/listing.ts +65 -10
- package/src/spoke/localhub.ts +14 -4
- package/src/spoke/locations.ts +40 -0
- package/src/spoke/profile-apply.ts +298 -0
- package/src/spoke/session-context.ts +256 -0
- package/src/spoke/spoke.ts +330 -23
- 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-mNzjlP_U.js +1 -0
- package/web/dist/assets/ArtifactPane-CPQcuX7c.js +1 -0
- package/web/dist/assets/ArtifactWorkbench-9eTj1CeW.css +1 -0
- package/web/dist/assets/ArtifactWorkbench-NoFC5Sb2.js +6 -0
- package/web/dist/assets/Library-DYsFj_5c.css +1 -0
- package/web/dist/assets/Library-LXHsP5Fw.js +2 -0
- package/web/dist/assets/ProfilesDestination-DhVVcAep.js +1 -0
- package/web/dist/assets/TeamsPage-DBrO5hoL.js +1 -0
- package/web/dist/assets/Watchtower-CTgqHk5g.css +1 -0
- package/web/dist/assets/Watchtower-DxaJYQp_.js +2 -0
- package/web/dist/assets/index-BabrNDB3.css +1 -0
- package/web/dist/assets/index-lIOfSYck.js +34 -0
- package/web/dist/assets/live-BDq0AABg.js +5 -0
- package/web/dist/assets/live-BUofsQpz.css +1 -0
- package/web/dist/assets/pane-layout-DWXfDU-1.js +1 -0
- package/web/dist/assets/plural-Bspvoqfq.js +1 -0
- package/web/dist/assets/renderer2d-DAr4IHM8.js +1 -0
- package/web/dist/assets/renderer3d-DWZj595u.js +5393 -0
- package/web/dist/assets/safe-area-JUb6OeI3.js +1 -0
- package/web/dist/assets/source-CDU04kYw.js +1 -0
- package/web/dist/assets/ui-Dh1-78Cl.js +3 -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,46 @@
|
|
|
1
|
+
// Role and policy-edge editing of a team's draft (HWA-37): small edits computed against the current draft spec (or
|
|
2
|
+
// the published one) and saved through TeamStore.saveDraft, which validates the whole spec again. Nothing here
|
|
3
|
+
// publishes: delivery and staffing keep using the published version until an explicit publish.
|
|
4
|
+
//
|
|
5
|
+
// - setRole: adds the role, or replaces the role of that name wholesale (its other fields are dropped).
|
|
6
|
+
// - removeRole: drops the role and every edge touching it; the entry role cannot be removed (name another first).
|
|
7
|
+
// - setEdge: replaces the edge drawn from → to (and, for a two-way edge, the one drawn to → from). An edge drawn the
|
|
8
|
+
// other way that already covers the pair is refused by the spec check (one edge per ordered pair).
|
|
9
|
+
// - removeEdge: drops the edge drawn from → to, or a two-way edge drawn to → from.
|
|
10
|
+
import { AgentError } from "../agent/errors"
|
|
11
|
+
import type { TeamSpec } from "./validation"
|
|
12
|
+
|
|
13
|
+
const isObj = (v: unknown): v is Record<string, unknown> => typeof v === "object" && v !== null && !Array.isArray(v)
|
|
14
|
+
|
|
15
|
+
/** A patch replacing the roles (and edges) for saveDraft. */
|
|
16
|
+
export interface DraftEdit {
|
|
17
|
+
readonly roles?: readonly unknown[]
|
|
18
|
+
readonly edges?: readonly unknown[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setRole(spec: TeamSpec, name: string, fields: Record<string, unknown>): DraftEdit {
|
|
22
|
+
if (fields.name !== undefined && fields.name !== name) throw new AgentError("invalid", "The role's name is in the path; to rename, add the new role and remove the old one")
|
|
23
|
+
const role = { ...fields, name }
|
|
24
|
+
const at = spec.roles.findIndex((r) => r.name === name)
|
|
25
|
+
const roles = at < 0 ? [...spec.roles, role] : spec.roles.map((r, i) => (i === at ? role : r))
|
|
26
|
+
return { roles }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function removeRole(spec: TeamSpec, name: string): DraftEdit {
|
|
30
|
+
if (!spec.roles.some((r) => r.name === name)) throw new AgentError("not_found", `The draft has no role ${name.slice(0, 40)}`)
|
|
31
|
+
if (spec.entryRole === name) throw new AgentError("invalid", `${name} is the entry role`, { hint: "Name another entry role first (team update entryRole)" })
|
|
32
|
+
return { roles: spec.roles.filter((r) => r.name !== name), edges: spec.edges.filter((e) => e.from !== name && e.to !== name) }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function setEdge(spec: TeamSpec, edge: Record<string, unknown>): DraftEdit {
|
|
36
|
+
if (!isObj(edge) || typeof edge.from !== "string" || typeof edge.to !== "string") throw new AgentError("invalid", "An edge needs from and to (role names)")
|
|
37
|
+
const { from, to } = edge
|
|
38
|
+
const replaced = (e: TeamSpec["edges"][number]) => (e.from === from && e.to === to) || (edge.direction === "two-way" && e.from === to && e.to === from)
|
|
39
|
+
return { edges: [...spec.edges.filter((e) => !replaced(e)), edge] }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function removeEdge(spec: TeamSpec, from: string, to: string): DraftEdit {
|
|
43
|
+
const drop = (e: TeamSpec["edges"][number]) => (e.from === from && e.to === to) || (e.direction === "two-way" && e.from === to && e.to === from)
|
|
44
|
+
if (!spec.edges.some(drop)) throw new AgentError("not_found", `The draft has no edge ${from.slice(0, 40)} → ${to.slice(0, 40)}`)
|
|
45
|
+
return { edges: spec.edges.filter((e) => !drop(e)) }
|
|
46
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Compact journal events of teams (HWA-33; contract TEAM_EVENT_TYPES, family `manage`). Allowlisted payloads only:
|
|
2
|
+
// IDs, enums (state, change), revisions, version numbers, generations and flags. Never names, titles, descriptions,
|
|
3
|
+
// role names or labels, responsibilities, tags, metadata, projects or any other definition text. The routes
|
|
4
|
+
// (HWA-37) emit them after a store call succeeds; HWA-34 owns `team.denied`, HWA-36 `team.intake`.
|
|
5
|
+
import type { EventInput } from "../agent/events"
|
|
6
|
+
import type { TeamSeatRecord } from "./seats"
|
|
7
|
+
import type { Team } from "./store"
|
|
8
|
+
|
|
9
|
+
/** What a `team.updated` changed. */
|
|
10
|
+
export type TeamChange = "draft" | "draft_discarded" | "published" | "paused" | "resumed"
|
|
11
|
+
|
|
12
|
+
export const teamCreatedEvent = (team: Team): EventInput => ({
|
|
13
|
+
type: "team.created",
|
|
14
|
+
family: "manage",
|
|
15
|
+
subject: { teamID: team.id },
|
|
16
|
+
data: { revision: team.revision, version: team.version, state: team.state },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export const teamUpdatedEvent = (team: Team, change: TeamChange): EventInput => ({
|
|
20
|
+
type: "team.updated",
|
|
21
|
+
family: "manage",
|
|
22
|
+
subject: { teamID: team.id },
|
|
23
|
+
data: { change, revision: team.revision, version: team.version, paused: team.paused === true },
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
/** Archived (`state: archived`) or restored (`state: active`). */
|
|
27
|
+
export const teamArchivedEvent = (team: Team): EventInput => ({
|
|
28
|
+
type: "team.archived",
|
|
29
|
+
family: "manage",
|
|
30
|
+
subject: { teamID: team.id },
|
|
31
|
+
data: { state: team.state, revision: team.revision, version: team.version },
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export const teamStaffedEvent = (seat: TeamSeatRecord): EventInput => ({
|
|
35
|
+
type: "team.staffed",
|
|
36
|
+
family: "manage",
|
|
37
|
+
subject: { teamID: seat.teamID, staffingID: seat.id, sessionID: seat.sessionID },
|
|
38
|
+
data: { generation: seat.generation, revision: seat.revision, version: seat.teamVersion },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
export const teamUnstaffedEvent = (seat: TeamSeatRecord): EventInput => ({
|
|
42
|
+
type: "team.unstaffed",
|
|
43
|
+
family: "manage",
|
|
44
|
+
subject: { teamID: seat.teamID, staffingID: seat.id, sessionID: seat.sessionID },
|
|
45
|
+
data: { generation: seat.generation, revision: seat.revision },
|
|
46
|
+
})
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Team intake routes on /api/agent (HWA-36; contract: Teams `POST /teams/:id/intake`, scope teams:submit). The rest
|
|
2
|
+
// of the teams surface (definitions, staffing, roster, teams:read|manage) is HWA-37's.
|
|
3
|
+
//
|
|
4
|
+
// POST /teams/:id/intake {title, brief?, priority?, labels?, due?, criteria?, ref?} Idempotency-Key
|
|
5
|
+
// GET /teams/:id/intake?status&limit&cursor → {items, cursor?}
|
|
6
|
+
// GET /teams/:id/intake/:intake → {intake}
|
|
7
|
+
// POST /teams/:id/intake/:intake/status {status, expectedRevision, reason?} → {intake, assignment?}
|
|
8
|
+
//
|
|
9
|
+
// Who (TeamIntakeService decides the details): users; app tokens with teams:submit (submit, read and cancel their
|
|
10
|
+
// own) — reads also accept teams:read/teams:manage once HWA-37 registers them; session tokens (submit under team
|
|
11
|
+
// policy, entry seats report status). Another user's or an unknown team: 404 not_found. Bodies are bounded here.
|
|
12
|
+
import { boundedText } from "../agent/body"
|
|
13
|
+
import type { AgentContext, AgentRoute } from "../agent/routes"
|
|
14
|
+
import { AgentError } from "../agent/errors"
|
|
15
|
+
import { idempotencyKeyOf, storeAnswer } from "../agent/idempotency"
|
|
16
|
+
import type { Store } from "../store"
|
|
17
|
+
import type { TeamIntakeService } from "./intake"
|
|
18
|
+
|
|
19
|
+
const PREFIX = "/api/agent/teams/:id/intake"
|
|
20
|
+
const NO_STORE = { "cache-control": "no-store" }
|
|
21
|
+
/** Request bodies (the brief is ≤ 16 000 characters). */
|
|
22
|
+
export const INTAKE_BODY_MAX = 96 * 1024
|
|
23
|
+
|
|
24
|
+
export interface IntakeRoutesDeps {
|
|
25
|
+
readonly intake: TeamIntakeService
|
|
26
|
+
readonly audit?: Pick<Store, "audit">
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const ok = (body: unknown) => Response.json(body, { headers: NO_STORE })
|
|
30
|
+
|
|
31
|
+
async function bodyOf(request: Request): Promise<Record<string, unknown>> {
|
|
32
|
+
const declared = Number(request.headers.get("content-length") ?? "0")
|
|
33
|
+
if (declared > INTAKE_BODY_MAX) throw new AgentError("invalid", `Body must be at most ${INTAKE_BODY_MAX} bytes`)
|
|
34
|
+
const text = await boundedText(request, INTAKE_BODY_MAX)
|
|
35
|
+
if (text.length > INTAKE_BODY_MAX) throw new AgentError("invalid", `Body must be at most ${INTAKE_BODY_MAX} bytes`)
|
|
36
|
+
let raw: unknown
|
|
37
|
+
try {
|
|
38
|
+
raw = JSON.parse(text === "" ? "{}" : text)
|
|
39
|
+
} catch {
|
|
40
|
+
throw new AgentError("invalid", "Body must be JSON")
|
|
41
|
+
}
|
|
42
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new AgentError("invalid", "Body must be a JSON object")
|
|
43
|
+
return raw as Record<string, unknown>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function intParam(url: URL, name: string): number | undefined {
|
|
47
|
+
const raw = url.searchParams.get(name)
|
|
48
|
+
return raw === null ? undefined : /^-?[0-9]{1,12}$/.test(raw) ? Number(raw) : Number.NaN
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function intakeRoutes(deps: IntakeRoutesDeps): AgentRoute[] {
|
|
52
|
+
const { intake } = deps
|
|
53
|
+
const audited = async (context: AgentContext, action: string, target: string) => {
|
|
54
|
+
await deps.audit?.audit({ actor: context.user.id, action, target, detail: context.actor.kind })
|
|
55
|
+
}
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
method: "POST",
|
|
59
|
+
path: PREFIX,
|
|
60
|
+
allow: { scope: "teams:submit", session: true },
|
|
61
|
+
async handle(context) {
|
|
62
|
+
const body = await bodyOf(context.request)
|
|
63
|
+
const key = idempotencyKeyOf(context.request)
|
|
64
|
+
const { intake: item, created } = await intake.submit(context.user.id, context.actor, context.params.id!, body, key === undefined ? {} : { idempotencyKey: key })
|
|
65
|
+
if (created) await audited(context, "team.intake.submit", item.id)
|
|
66
|
+
return storeAnswer(created, { intake: item })
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
method: "GET",
|
|
71
|
+
path: PREFIX,
|
|
72
|
+
allow: { app: true, session: true },
|
|
73
|
+
async handle({ url, user, actor, params }) {
|
|
74
|
+
const limit = intParam(url, "limit")
|
|
75
|
+
const status = url.searchParams.get("status") || undefined
|
|
76
|
+
const cursor = url.searchParams.get("cursor") || undefined
|
|
77
|
+
return ok(await intake.list(user.id, actor, params.id!, { ...(limit !== undefined ? { limit } : {}), ...(status ? { status } : {}), ...(cursor ? { cursor } : {}) }))
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
method: "GET",
|
|
82
|
+
path: `${PREFIX}/:intake`,
|
|
83
|
+
allow: { app: true, session: true },
|
|
84
|
+
async handle({ user, actor, params }) {
|
|
85
|
+
return ok({ intake: await intake.get(user.id, actor, params.id!, params.intake!) })
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
method: "POST",
|
|
90
|
+
path: `${PREFIX}/:intake/status`,
|
|
91
|
+
allow: { app: true, session: true },
|
|
92
|
+
async handle(context) {
|
|
93
|
+
const body = await bodyOf(context.request)
|
|
94
|
+
const result = await intake.setStatus(context.user.id, context.actor, context.params.id!, context.params.intake!, body)
|
|
95
|
+
await audited(context, `team.intake.${result.intake.status === "routed" ? "progress" : result.intake.status}`, result.intake.id)
|
|
96
|
+
return ok(result)
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
}
|