@defrex/autobuild 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/LICENSE +201 -0
- package/README.md +192 -0
- package/SPEC.md +2269 -0
- package/bin/ab-build-runner.ts +47 -0
- package/bin/ab-dev.ts +27 -0
- package/bin/ab-dispatch-kernel.ts +92 -0
- package/bin/ab-harvest-runner.ts +29 -0
- package/bin/ab-init-probe.ts +19 -0
- package/bin/ab-process-group-reaper.ts +32 -0
- package/bin/ab.ts +6 -0
- package/bin/agent/ab +3 -0
- package/docs/answering-blocked-builds.md +89 -0
- package/docs/architecture.md +726 -0
- package/docs/assets/headline-wide.png +0 -0
- package/docs/configuration.md +1623 -0
- package/docs/hosted-dispatcher-cutover-validation.md +178 -0
- package/docs/hosted-dispatcher.md +204 -0
- package/docs/mcp.md +119 -0
- package/docs/operator-api.md +186 -0
- package/docs/remote-store-protocol.md +972 -0
- package/docs/remote-ticket-protocol.md +79 -0
- package/docs/setup.md +289 -0
- package/docs/spec-standard.md +63 -0
- package/docs/ticket-dependencies.md +59 -0
- package/docs/vercel-sandbox-rollout-probe.md +7 -0
- package/package.json +135 -0
- package/packages/core/src/cli/args.ts +74 -0
- package/packages/core/src/cli/artifact.ts +182 -0
- package/packages/core/src/cli/binary.ts +198 -0
- package/packages/core/src/cli/build-control.ts +789 -0
- package/packages/core/src/cli/build-progress.ts +55 -0
- package/packages/core/src/cli/bulk-control.ts +385 -0
- package/packages/core/src/cli/context.ts +470 -0
- package/packages/core/src/cli/dashboard/actions.ts +59 -0
- package/packages/core/src/cli/dashboard/cells.ts +92 -0
- package/packages/core/src/cli/dashboard/composer.ts +230 -0
- package/packages/core/src/cli/dashboard/detail.ts +100 -0
- package/packages/core/src/cli/dashboard/frame-image.ts +373 -0
- package/packages/core/src/cli/dashboard/live.ts +152 -0
- package/packages/core/src/cli/dashboard/model.ts +1173 -0
- package/packages/core/src/cli/dashboard/poll.ts +196 -0
- package/packages/core/src/cli/dashboard/render.ts +1568 -0
- package/packages/core/src/cli/dashboard/selection.ts +66 -0
- package/packages/core/src/cli/dashboard/session-feed.ts +244 -0
- package/packages/core/src/cli/dashboard/session-view.ts +506 -0
- package/packages/core/src/cli/dashboard/transcript.ts +108 -0
- package/packages/core/src/cli/dev.ts +46 -0
- package/packages/core/src/cli/dispatch-frontend.ts +1045 -0
- package/packages/core/src/cli/dispatch-process.ts +259 -0
- package/packages/core/src/cli/dispatch.ts +3692 -0
- package/packages/core/src/cli/dotenv.ts +48 -0
- package/packages/core/src/cli/env.ts +206 -0
- package/packages/core/src/cli/harvest.ts +698 -0
- package/packages/core/src/cli/help.ts +713 -0
- package/packages/core/src/cli/init-agent.ts +106 -0
- package/packages/core/src/cli/init-validation.ts +583 -0
- package/packages/core/src/cli/init.ts +756 -0
- package/packages/core/src/cli/installation.ts +333 -0
- package/packages/core/src/cli/keyboard.ts +261 -0
- package/packages/core/src/cli/main.ts +1399 -0
- package/packages/core/src/cli/mcp.ts +279 -0
- package/packages/core/src/cli/models.ts +65 -0
- package/packages/core/src/cli/observe.ts +55 -0
- package/packages/core/src/cli/plugin.ts +227 -0
- package/packages/core/src/cli/pr-attachments.ts +138 -0
- package/packages/core/src/cli/pr-summary.ts +124 -0
- package/packages/core/src/cli/publication-settlement.ts +188 -0
- package/packages/core/src/cli/repo-state.ts +213 -0
- package/packages/core/src/cli/repository-status.ts +80 -0
- package/packages/core/src/cli/self-update.ts +443 -0
- package/packages/core/src/cli/status.ts +895 -0
- package/packages/core/src/cli/store-opening.contract.ts +289 -0
- package/packages/core/src/cli/store-opening.ts +167 -0
- package/packages/core/src/cli/store-ref.ts +33 -0
- package/packages/core/src/cli/terminal-restore.ts +174 -0
- package/packages/core/src/cli/terminal.ts +361 -0
- package/packages/core/src/cli/terminals.ts +995 -0
- package/packages/core/src/cli/testkit.ts +194 -0
- package/packages/core/src/cli/ticket.ts +484 -0
- package/packages/core/src/cli/upgrade-agent.ts +180 -0
- package/packages/core/src/cli/upgrade-commit.ts +468 -0
- package/packages/core/src/cli/upgrade-notice.ts +91 -0
- package/packages/core/src/cli/upgrade-progress.ts +110 -0
- package/packages/core/src/cli/upgrade.ts +888 -0
- package/packages/core/src/cli/wait.ts +587 -0
- package/packages/core/src/cli/watch.ts +932 -0
- package/packages/core/src/config/live.ts +255 -0
- package/packages/core/src/config/load.ts +98 -0
- package/packages/core/src/config/roles.ts +438 -0
- package/packages/core/src/config/schema.ts +774 -0
- package/packages/core/src/distribution.ts +24 -0
- package/packages/core/src/events/catalog.ts +201 -0
- package/packages/core/src/events/envelope.ts +59 -0
- package/packages/core/src/events/payloads.ts +618 -0
- package/packages/core/src/events/repository.ts +559 -0
- package/packages/core/src/events/sessions.ts +180 -0
- package/packages/core/src/harvest/schema.ts +147 -0
- package/packages/core/src/ids.ts +45 -0
- package/packages/core/src/integration/harness.ts +681 -0
- package/packages/core/src/kernel/auto-merge.ts +259 -0
- package/packages/core/src/kernel/converge.ts +117 -0
- package/packages/core/src/kernel/dispatch-settings.ts +60 -0
- package/packages/core/src/kernel/dispatch-status.ts +141 -0
- package/packages/core/src/kernel/engine.ts +1047 -0
- package/packages/core/src/kernel/harvest.ts +823 -0
- package/packages/core/src/kernel/origin.ts +69 -0
- package/packages/core/src/kernel/phases.ts +196 -0
- package/packages/core/src/kernel/plan-verify-selection.ts +105 -0
- package/packages/core/src/kernel/pr-attachments.ts +82 -0
- package/packages/core/src/kernel/reducer.ts +752 -0
- package/packages/core/src/kernel/stall.ts +155 -0
- package/packages/core/src/kernel/verify-applicability.ts +52 -0
- package/packages/core/src/markdown.ts +67 -0
- package/packages/core/src/ontology.ts +288 -0
- package/packages/core/src/open-map.ts +203 -0
- package/packages/core/src/operator/annotations.ts +187 -0
- package/packages/core/src/operator/client.ts +311 -0
- package/packages/core/src/operator/control.ts +117 -0
- package/packages/core/src/operator/index.ts +62 -0
- package/packages/core/src/operator/presentation.ts +22 -0
- package/packages/core/src/operator/protocol.ts +98 -0
- package/packages/core/src/operator/query.ts +201 -0
- package/packages/core/src/operator/registry.ts +1043 -0
- package/packages/core/src/operator/requests.ts +138 -0
- package/packages/core/src/operator/sandbox.ts +458 -0
- package/packages/core/src/operator/server.ts +616 -0
- package/packages/core/src/operator/sessions.ts +223 -0
- package/packages/core/src/operator/tickets.ts +177 -0
- package/packages/core/src/plugin-sdk/index.ts +174 -0
- package/packages/core/src/plugins/contract-entry.ts +74 -0
- package/packages/core/src/plugins/contract-env.ts +3 -0
- package/packages/core/src/plugins/load.ts +259 -0
- package/packages/core/src/plugins/manifest.ts +236 -0
- package/packages/core/src/plugins/registry.ts +274 -0
- package/packages/core/src/plugins/runtimes.ts +75 -0
- package/packages/core/src/ports/forge/contract.ts +258 -0
- package/packages/core/src/ports/forge/create.ts +74 -0
- package/packages/core/src/ports/forge/fake.ts +488 -0
- package/packages/core/src/ports/forge/github-pr-attachments.ts +351 -0
- package/packages/core/src/ports/forge/github-transport.ts +352 -0
- package/packages/core/src/ports/forge/github.ts +1162 -0
- package/packages/core/src/ports/forge/local-git.ts +568 -0
- package/packages/core/src/ports/runner/claude.ts +873 -0
- package/packages/core/src/ports/runner/cli-stream.ts +90 -0
- package/packages/core/src/ports/runner/codex.ts +873 -0
- package/packages/core/src/ports/runner/contract.ts +492 -0
- package/packages/core/src/ports/runner/fake.ts +213 -0
- package/packages/core/src/ports/runner/json-record.ts +2 -0
- package/packages/core/src/ports/runner/live-contract-fixture.ts +176 -0
- package/packages/core/src/ports/runner/one-shot.ts +29 -0
- package/packages/core/src/ports/runner/pi-bridge-core.ts +136 -0
- package/packages/core/src/ports/runner/pi-bridge.js +6 -0
- package/packages/core/src/ports/runner/pi-rpc.ts +454 -0
- package/packages/core/src/ports/runner/pi.ts +617 -0
- package/packages/core/src/ports/runner/production.ts +74 -0
- package/packages/core/src/ports/runner/provider-error.ts +121 -0
- package/packages/core/src/ports/runner/routing.ts +234 -0
- package/packages/core/src/ports/runner/runtime.ts +214 -0
- package/packages/core/src/ports/runner/session-env.ts +34 -0
- package/packages/core/src/ports/runner/stream-parts.ts +103 -0
- package/packages/core/src/ports/tickets/contract.ts +570 -0
- package/packages/core/src/ports/tickets/create.ts +135 -0
- package/packages/core/src/ports/tickets/fake.ts +215 -0
- package/packages/core/src/ports/tickets/file.ts +559 -0
- package/packages/core/src/ports/tickets/linear.ts +811 -0
- package/packages/core/src/ports/tickets/operations.ts +161 -0
- package/packages/core/src/ports/tickets/remote-index.ts +13 -0
- package/packages/core/src/ports/tickets/remote-protocol.ts +80 -0
- package/packages/core/src/ports/tickets/remote-server.ts +185 -0
- package/packages/core/src/ports/tickets/remote.ts +130 -0
- package/packages/core/src/ports/tickets/update.ts +51 -0
- package/packages/core/src/ports/types.ts +528 -0
- package/packages/core/src/ports/workspace/build-execution.ts +123 -0
- package/packages/core/src/ports/workspace/contract.ts +136 -0
- package/packages/core/src/ports/workspace/create.ts +132 -0
- package/packages/core/src/ports/workspace/distribution-archive.ts +317 -0
- package/packages/core/src/ports/workspace/fake.ts +432 -0
- package/packages/core/src/ports/workspace/git-worktree.ts +848 -0
- package/packages/core/src/ports/workspace/harvest-execution.ts +111 -0
- package/packages/core/src/ports/workspace/in-process-build-execution.ts +30 -0
- package/packages/core/src/ports/workspace/local-build-execution.ts +88 -0
- package/packages/core/src/ports/workspace/operator-sandbox.ts +142 -0
- package/packages/core/src/ports/workspace/phase-scratch.ts +156 -0
- package/packages/core/src/ports/workspace/vercel-sandbox.ts +2541 -0
- package/packages/core/src/processes/build-child-exit.ts +78 -0
- package/packages/core/src/processes/build-child.ts +119 -0
- package/packages/core/src/processes/build-execution-state.ts +95 -0
- package/packages/core/src/processes/build-parent-watch.ts +35 -0
- package/packages/core/src/processes/build-runner.ts +2614 -0
- package/packages/core/src/processes/dispatcher.ts +2825 -0
- package/packages/core/src/processes/execution-settlement.ts +171 -0
- package/packages/core/src/processes/harvest-child.ts +115 -0
- package/packages/core/src/processes/harvest-execution-state.ts +121 -0
- package/packages/core/src/processes/harvest-runner.ts +1656 -0
- package/packages/core/src/processes/harvest.ts +595 -0
- package/packages/core/src/processes/infrastructure-failure-budget.ts +146 -0
- package/packages/core/src/processes/phase-failure-budget.ts +52 -0
- package/packages/core/src/processes/process-group-reaper.ts +35 -0
- package/packages/core/src/processes/process-group.ts +65 -0
- package/packages/core/src/processes/publication-loss.ts +62 -0
- package/packages/core/src/processes/publication-state.ts +93 -0
- package/packages/core/src/processes/sandbox-state.ts +101 -0
- package/packages/core/src/registry.ts +24 -0
- package/packages/core/src/sessions/reducer.ts +185 -0
- package/packages/core/src/skills.ts +7 -0
- package/packages/core/src/spec-standard.ts +47 -0
- package/packages/core/src/store/adapter.ts +80 -0
- package/packages/core/src/store/build-scope.ts +272 -0
- package/packages/core/src/store/contract.ts +2581 -0
- package/packages/core/src/store/local/blobs.ts +38 -0
- package/packages/core/src/store/local/schema.ts +180 -0
- package/packages/core/src/store/local/store.ts +1713 -0
- package/packages/core/src/store/memory.ts +1138 -0
- package/packages/core/src/store/phase-session.ts +425 -0
- package/packages/core/src/store/remote/client.ts +780 -0
- package/packages/core/src/store/remote/index.ts +12 -0
- package/packages/core/src/store/remote/protocol.ts +348 -0
- package/packages/core/src/store/remote/server.ts +923 -0
- package/packages/core/src/store/remote/token.ts +137 -0
- package/packages/core/src/store/remote/version.ts +10 -0
- package/packages/core/src/store/retention.ts +91 -0
- package/packages/core/src/store/session-handle.ts +265 -0
- package/packages/core/src/store/streams/assemble.ts +79 -0
- package/packages/core/src/store/streams/lock.ts +42 -0
- package/packages/core/src/store/streams/session-writer.ts +306 -0
- package/packages/core/src/store/streams/types.ts +171 -0
- package/packages/core/src/store/streams/wait.ts +127 -0
- package/packages/core/src/store/subscribe.ts +105 -0
- package/packages/core/src/store/types.ts +466 -0
- package/packages/core/src/testing/fixed.ts +44 -0
- package/packages/core/src/testing/store-failures.ts +32 -0
- package/packages/core/src/zod-issues.ts +91 -0
- package/skills/code-review/SKILL.md +100 -0
- package/skills/finalize/SKILL.md +44 -0
- package/skills/guide/SKILL.md +1781 -0
- package/skills/guide/references/operator-api.md +164 -0
- package/skills/guide/references/plugin-authoring.md +354 -0
- package/skills/guide/references/remote-store-protocol.md +972 -0
- package/skills/guide/references/remote-ticket-protocol.md +79 -0
- package/skills/guide/references/setup.md +289 -0
- package/skills/guide/references/spec-standard.md +63 -0
- package/skills/guide/references/ticket-dependencies.md +59 -0
- package/skills/harvest/SKILL.md +66 -0
- package/skills/harvest-review/SKILL.md +63 -0
- package/skills/implement/SKILL.md +82 -0
- package/skills/plan/SKILL.md +123 -0
- package/skills/plan-review/SKILL.md +121 -0
- package/skills/reconcile/SKILL.md +65 -0
- package/skills/spec/SKILL.md +132 -0
- package/skills/tickets/SKILL.md +139 -0
- package/templates/autobuild.toml +36 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Pure stable-identity selection for every selectable dashboard row. */
|
|
2
|
+
import type { DashboardModel, DashboardSelection } from './model'
|
|
3
|
+
|
|
4
|
+
/** Render order is selection order: the always-present process-global row,
|
|
5
|
+
* optional repository harvest, then the model's already slug-sorted builds. */
|
|
6
|
+
export function dashboardSelections(
|
|
7
|
+
model: Pick<DashboardModel, 'harvest' | 'builds'>,
|
|
8
|
+
): DashboardSelection[] {
|
|
9
|
+
return [
|
|
10
|
+
{ kind: 'global' },
|
|
11
|
+
...(model.harvest !== undefined ? [{ kind: 'harvest' } as const] : []),
|
|
12
|
+
...model.builds.map((build) => ({ kind: 'build' as const, slug: build.slug })),
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sameSelection(
|
|
17
|
+
left: DashboardSelection | undefined,
|
|
18
|
+
right: DashboardSelection | undefined,
|
|
19
|
+
): boolean {
|
|
20
|
+
if (left === undefined || right === undefined) return left === right
|
|
21
|
+
return (
|
|
22
|
+
left.kind === right.kind &&
|
|
23
|
+
(left.kind === 'global' ||
|
|
24
|
+
left.kind === 'harvest' ||
|
|
25
|
+
(right.kind === 'build' && left.slug === right.slug))
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function initialSelection(
|
|
30
|
+
rows: readonly DashboardSelection[],
|
|
31
|
+
): DashboardSelection | undefined {
|
|
32
|
+
return rows[0]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Move by one (or any supplied delta) without wrapping. */
|
|
36
|
+
export function moveSelection(
|
|
37
|
+
rows: readonly DashboardSelection[],
|
|
38
|
+
selected: DashboardSelection | undefined,
|
|
39
|
+
delta: number,
|
|
40
|
+
): DashboardSelection | undefined {
|
|
41
|
+
if (rows.length === 0) return undefined
|
|
42
|
+
const current = selected === undefined ? 0 : rows.findIndex((row) => sameSelection(row, selected))
|
|
43
|
+
const index = current === -1 ? 0 : current
|
|
44
|
+
const next = Math.max(0, Math.min(rows.length - 1, index + delta))
|
|
45
|
+
return rows[next]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Preserve the selected ROW across repaints, harvest appearance, and build
|
|
50
|
+
* re-sorts. If it disappears, its old row index chooses the successor now
|
|
51
|
+
* occupying that row, or the final predecessor when the old row was last.
|
|
52
|
+
* Only an empty list clears selection.
|
|
53
|
+
*/
|
|
54
|
+
export function reconcileSelection(
|
|
55
|
+
previousRows: readonly DashboardSelection[],
|
|
56
|
+
nextRows: readonly DashboardSelection[],
|
|
57
|
+
selected: DashboardSelection | undefined,
|
|
58
|
+
): DashboardSelection | undefined {
|
|
59
|
+
if (nextRows.length === 0) return undefined
|
|
60
|
+
if (selected === undefined) return initialSelection(nextRows)
|
|
61
|
+
const retained = nextRows.find((row) => sameSelection(row, selected))
|
|
62
|
+
if (retained !== undefined) return retained
|
|
63
|
+
const oldIndex = previousRows.findIndex((row) => sameSelection(row, selected))
|
|
64
|
+
if (oldIndex === -1) return initialSelection(nextRows)
|
|
65
|
+
return nextRows[Math.min(oldIndex, nextRows.length - 1)]
|
|
66
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session view's live-read cycle (SPEC §9): ONE module both controllers
|
|
3
|
+
* call, so the follow/close/fallback semantics cannot drift between the
|
|
4
|
+
* `DispatchLoop` (the wire-injected path the capture harness exercises) and
|
|
5
|
+
* `DispatchFrontend` (the production interactive path).
|
|
6
|
+
*
|
|
7
|
+
* `SessionStreamFeed.poll(view)` performs one immediate store read
|
|
8
|
+
* (`waitSeconds: 0` — the store waits only when `waitSeconds > 0`), so no read
|
|
9
|
+
* can hold a frame longer than the controller's poll interval, and the writer's
|
|
10
|
+
* 250 ms flush cadence puts new parts on screen within one poll. It returns the
|
|
11
|
+
* next view fields — appended parts, advanced `lastSeq`, status/outcome on
|
|
12
|
+
* close, the pruned-chunk artifact fallback, or an error message that the next
|
|
13
|
+
* poll retries — and never throws into the frame.
|
|
14
|
+
*
|
|
15
|
+
* The returned update carries the identity and `since` cursor it started from;
|
|
16
|
+
* `applySessionFeedUpdate` is the apply-fence: a read that raced an Escape, a
|
|
17
|
+
* session switch, or a newer apply is discarded instead of retargeting the
|
|
18
|
+
* view. The stored part sequence is only ever appended to — the view never
|
|
19
|
+
* rewrites history.
|
|
20
|
+
*/
|
|
21
|
+
import type { UIMessage } from 'ai'
|
|
22
|
+
import type { StreamOutcome, StreamPart, StreamRead } from '../../store/streams/types'
|
|
23
|
+
import type { SessionDashboardView } from './model'
|
|
24
|
+
|
|
25
|
+
/** The read-only store surface the feed consumes (injected; no terminal). */
|
|
26
|
+
export interface SessionFeedStore {
|
|
27
|
+
readStream(streamId: string, opts?: { since?: number; waitSeconds?: number }): Promise<StreamRead>
|
|
28
|
+
getArtifact(
|
|
29
|
+
slug: string,
|
|
30
|
+
kind: string,
|
|
31
|
+
rev?: number,
|
|
32
|
+
): Promise<{
|
|
33
|
+
content: Uint8Array | string
|
|
34
|
+
} | null>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The mutable fields of a session view the feed may produce. */
|
|
38
|
+
export interface SessionViewFields {
|
|
39
|
+
status: 'open' | 'closed'
|
|
40
|
+
outcome?: StreamOutcome
|
|
41
|
+
source:
|
|
42
|
+
| { kind: 'parts'; parts: StreamPart[]; lastSeq: number }
|
|
43
|
+
| { kind: 'document'; document: UIMessage[] }
|
|
44
|
+
error?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The identity-bearing result of one poll: `since` is the cursor the read
|
|
48
|
+
* started from (the parts `lastSeq`, or 0 for a document retry), and
|
|
49
|
+
* everything is applied only through the fence below. */
|
|
50
|
+
export interface SessionFeedUpdate {
|
|
51
|
+
slug: string
|
|
52
|
+
sessionId: string
|
|
53
|
+
stream: string
|
|
54
|
+
since: number
|
|
55
|
+
/** Which source shape this update was polled from — the fence compares it
|
|
56
|
+
* against the current view so a read that raced a source switch is
|
|
57
|
+
* discarded. */
|
|
58
|
+
polledSource: 'parts' | 'document'
|
|
59
|
+
fields: SessionViewFields
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The shape of a session view the feed polls. */
|
|
63
|
+
export type PollableSessionView = SessionDashboardView
|
|
64
|
+
|
|
65
|
+
function errorMessage(error: unknown): string {
|
|
66
|
+
return error instanceof Error ? error.message : String(error)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseDocument(content: Uint8Array | string): UIMessage[] | undefined {
|
|
70
|
+
try {
|
|
71
|
+
const text = typeof content === 'string' ? content : new TextDecoder().decode(content)
|
|
72
|
+
const parsed: unknown = JSON.parse(text)
|
|
73
|
+
if (!Array.isArray(parsed)) return undefined
|
|
74
|
+
return parsed as UIMessage[]
|
|
75
|
+
} catch {
|
|
76
|
+
return undefined
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class SessionStreamFeed {
|
|
81
|
+
constructor(
|
|
82
|
+
private readonly store: SessionFeedStore,
|
|
83
|
+
private readonly slug: string,
|
|
84
|
+
) {}
|
|
85
|
+
|
|
86
|
+
/** One immediate read cycle for the given view. Returns `undefined` when
|
|
87
|
+
* nothing changed (a document-source view is final; an open stream with no
|
|
88
|
+
* new chunks leaves the view alone). Errors are retained as view fields and
|
|
89
|
+
* retried by the next poll, never thrown. */
|
|
90
|
+
async poll(view: PollableSessionView): Promise<SessionFeedUpdate | undefined> {
|
|
91
|
+
// A finalized document is the end state; only a failed read of it retries.
|
|
92
|
+
if (view.source.kind === 'document' && view.error === undefined) return undefined
|
|
93
|
+
|
|
94
|
+
const polledSource = view.source.kind
|
|
95
|
+
const since = view.source.kind === 'parts' ? view.source.lastSeq : 0
|
|
96
|
+
const base: SessionViewFields = {
|
|
97
|
+
status: view.status,
|
|
98
|
+
...(view.outcome !== undefined ? { outcome: view.outcome } : {}),
|
|
99
|
+
source: view.source,
|
|
100
|
+
...(view.error !== undefined ? { error: view.error } : {}),
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const read = await this.store.readStream(this.streamId(view), { since, waitSeconds: 0 })
|
|
104
|
+
|
|
105
|
+
// A clean close appends no chunk, so a closed read with no new chunks
|
|
106
|
+
// is the normal close signal — not a reason to swap sources. When the
|
|
107
|
+
// view already holds part content, keep it: `error` and `abort` lines
|
|
108
|
+
// exist only on the parts path, and a failed artifact read must never
|
|
109
|
+
// turn a healthy view into an error view. The finalized-artifact
|
|
110
|
+
// fallback applies only when there is nothing to keep — a closed
|
|
111
|
+
// session opened to an empty (e.g. pruned) chunk log.
|
|
112
|
+
if (
|
|
113
|
+
read.status === 'closed' &&
|
|
114
|
+
read.chunks.length === 0 &&
|
|
115
|
+
view.source.kind === 'parts' &&
|
|
116
|
+
view.source.parts.length === 0
|
|
117
|
+
) {
|
|
118
|
+
return await this.artifactFallback(view, base, since, polledSource, read)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A document-source view only polls to retry a failed read; the
|
|
122
|
+
// finalized content is already in hand, so a successful read just
|
|
123
|
+
// clears the error and tracks the stream's status/outcome.
|
|
124
|
+
if (view.source.kind === 'document') {
|
|
125
|
+
const outcome = read.outcome ?? base.outcome
|
|
126
|
+
if (read.status === base.status && outcome === base.outcome && base.error === undefined)
|
|
127
|
+
return undefined
|
|
128
|
+
return this.update(view, since, polledSource, {
|
|
129
|
+
status: read.status,
|
|
130
|
+
...(outcome !== undefined ? { outcome } : {}),
|
|
131
|
+
source: view.source,
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const parts = view.source.kind === 'parts' ? [...view.source.parts] : []
|
|
136
|
+
let lastSeq = since
|
|
137
|
+
for (const chunk of read.chunks) {
|
|
138
|
+
parts.push(...chunk.parts)
|
|
139
|
+
if (chunk.seq > lastSeq) lastSeq = chunk.seq
|
|
140
|
+
}
|
|
141
|
+
const fields: SessionViewFields = {
|
|
142
|
+
status: read.status,
|
|
143
|
+
...(read.outcome !== undefined ? { outcome: read.outcome } : {}),
|
|
144
|
+
source: { kind: 'parts', parts, lastSeq },
|
|
145
|
+
}
|
|
146
|
+
if (
|
|
147
|
+
read.chunks.length === 0 &&
|
|
148
|
+
read.status === base.status &&
|
|
149
|
+
read.outcome === base.outcome &&
|
|
150
|
+
base.error === undefined
|
|
151
|
+
) {
|
|
152
|
+
return undefined
|
|
153
|
+
}
|
|
154
|
+
return this.update(view, since, polledSource, fields)
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return this.update(view, since, polledSource, { ...base, error: errorMessage(error) })
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Read the closed stream's deposited document artifact. A failed or absent
|
|
161
|
+
* read keeps the view's current content and carries the error for the next
|
|
162
|
+
* poll to retry. */
|
|
163
|
+
private async artifactFallback(
|
|
164
|
+
view: PollableSessionView,
|
|
165
|
+
base: SessionViewFields,
|
|
166
|
+
since: number,
|
|
167
|
+
polledSource: 'parts' | 'document',
|
|
168
|
+
read: StreamRead,
|
|
169
|
+
): Promise<SessionFeedUpdate> {
|
|
170
|
+
try {
|
|
171
|
+
const artifact = await this.store.getArtifact(this.slug, `stream:${this.streamId(view)}`, 0)
|
|
172
|
+
const document = artifact === null ? undefined : parseDocument(artifact.content)
|
|
173
|
+
if (document === undefined) {
|
|
174
|
+
return this.update(view, since, polledSource, {
|
|
175
|
+
...base,
|
|
176
|
+
status: 'closed',
|
|
177
|
+
...(read.outcome !== undefined ? { outcome: read.outcome } : {}),
|
|
178
|
+
error:
|
|
179
|
+
artifact === null
|
|
180
|
+
? `stream artifact stream:${this.streamId(view)} is not retrievable`
|
|
181
|
+
: 'stream artifact is not a readable session document',
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
return this.update(view, since, polledSource, {
|
|
185
|
+
status: 'closed',
|
|
186
|
+
...(read.outcome !== undefined ? { outcome: read.outcome } : {}),
|
|
187
|
+
source: { kind: 'document', document },
|
|
188
|
+
})
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return this.update(view, since, polledSource, {
|
|
191
|
+
...base,
|
|
192
|
+
status: 'closed',
|
|
193
|
+
...(read.outcome !== undefined ? { outcome: read.outcome } : {}),
|
|
194
|
+
error: errorMessage(error),
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private streamId(view: PollableSessionView): string {
|
|
200
|
+
return view.stream
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private update(
|
|
204
|
+
view: PollableSessionView,
|
|
205
|
+
since: number,
|
|
206
|
+
polledSource: 'parts' | 'document',
|
|
207
|
+
fields: SessionViewFields,
|
|
208
|
+
): SessionFeedUpdate {
|
|
209
|
+
return {
|
|
210
|
+
slug: view.slug,
|
|
211
|
+
sessionId: view.sessionId,
|
|
212
|
+
stream: view.stream,
|
|
213
|
+
since,
|
|
214
|
+
polledSource,
|
|
215
|
+
fields,
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The apply-fence: apply a feed update to `current` only when it is still the
|
|
222
|
+
* same session view this update was polled from, at the same cursor. Anything
|
|
223
|
+
* else — an Escape to detail, a session switch, a newer apply — rejects the
|
|
224
|
+
* stale read by returning `undefined`.
|
|
225
|
+
*/
|
|
226
|
+
export function applySessionFeedUpdate(
|
|
227
|
+
current: SessionDashboardView | undefined,
|
|
228
|
+
update: SessionFeedUpdate,
|
|
229
|
+
): SessionDashboardView | undefined {
|
|
230
|
+
if (current === undefined) return undefined
|
|
231
|
+
if (
|
|
232
|
+
current.slug !== update.slug ||
|
|
233
|
+
current.sessionId !== update.sessionId ||
|
|
234
|
+
current.stream !== update.stream
|
|
235
|
+
) {
|
|
236
|
+
return undefined
|
|
237
|
+
}
|
|
238
|
+
if (update.polledSource === 'parts') {
|
|
239
|
+
if (current.source.kind !== 'parts' || current.source.lastSeq !== update.since) return undefined
|
|
240
|
+
} else if (current.source.kind !== 'document') {
|
|
241
|
+
return undefined
|
|
242
|
+
}
|
|
243
|
+
return { ...current, ...update.fields }
|
|
244
|
+
}
|