refactor(jobs): type the producer face by the record declaration, tidy review residue
- `JobStart` is a union discriminated by `record`: `RecordingJobStart.run` receives `RecordingJob` (with `append`), `PlainJobStart.run` receives the plain `RunningJob`, and the registry builds the plain face without the method, so the former warn-and-drop path is unrepresentable. - Remove the duplicated JSDoc opener in the registry seam and correct the job-list slot comment (order 20 sits before the subagent catalog's 30). - Restore the `cordis-tool-round` session fixture whose only change was a re-recorded `createdAt`. - Update the 2026-08-08 job-display note's stale service-disposal and slot ordering facts; archive the superseded 2026-08-25 unified-task-list note and point its remaining inbound links at the archived snapshot.
This commit is contained in:
1 parent
6a4d7ee242
commit
b48c05d117
35 files changed
+326
-212
No files matched your search
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/jobs.md
|
||||
jobs.md: 0610d99892da22a25590ab7efe8ff71d9a004f01
|
||||
jobs.zh.md: 4c35fd7c879ddf7133c0db55df24948b40118b9d
|
||||
jobs.md: 639faa8da3836cb3efc865c494cd8ca8c60f49fb
|
||||
jobs.zh.md: 3d008671657847520eccd41b8a3bd75014777680
|
||||
+55
-47
@@ -23,42 +23,37 @@ interface JobKindMap {
|
||||
|
||||
## Producer contract
|
||||
|
||||
`JobStart` declares identity and a starter. The runtime finishes preflight before calling `run()` with the job's producer face and commits without a later failable step. Producers own execution resources; the runtime owns identity, access, and lifecycle state.
|
||||
`JobStart` declares identity and a starter, discriminated by `record`: a `RecordingJobStart` receives the `RecordingJob` face, a `PlainJobStart` the `RunningJob` face without `append`. The runtime finishes preflight before calling `run()` with that face and commits without a later failable step. Producers own execution resources; the runtime owns identity, access, and lifecycle state.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Producer declaration passed to {@link JobRegistry.start}. The runtime
|
||||
* preflights access and cleanup before invoking {@link run}; the producer owns
|
||||
* execution resources while the runtime owns identity and lifecycle state.
|
||||
* A start with the model-facing surfaces only. Its producer face carries no
|
||||
* `append`, so output the registry would have nowhere to keep is
|
||||
* unrepresentable rather than dropped.
|
||||
*/
|
||||
interface JobStart {
|
||||
/** Producer kind — also the id prefix (`bash`, `subagent`, …). */
|
||||
kind: JobKind
|
||||
/** One-line model-facing label (the command; the delegation description). */
|
||||
label: string
|
||||
interface PlainJobStart extends JobStartBase {
|
||||
record?: undefined
|
||||
/**
|
||||
* Optional UTF-8 byte cap for each complete model-facing completion notice or
|
||||
* output read, including controller status metadata. Independent of record
|
||||
* retention: it bounds the consuming model surface, never
|
||||
* {@link JobRegistry.readRecord}.
|
||||
* Start the work after preflight and synchronously return its hooks. Called
|
||||
* once with the job's producer face; a throw leaves nothing registered (the
|
||||
* spent ordinal is skipped), and the producer must clean up any partially
|
||||
* started resources.
|
||||
* @param job - the issued id plus the live-detail writer.
|
||||
*/
|
||||
outputLimitBytes?: number
|
||||
/**
|
||||
* Owning live agent. Access is fenced by its session id, and agent disposal
|
||||
* cancels and awaits the job. The instance must be the one currently
|
||||
* registered under its agent id. Omitting the owner creates an unowned job,
|
||||
* open to any caller until service disposal.
|
||||
*/
|
||||
owner?: Agent
|
||||
/**
|
||||
* Declares an observable output record beside the model-facing surfaces:
|
||||
* {@link RunningJob.append} retains chunks in a bounded ring that any number
|
||||
* of observers read at absolute byte offsets through
|
||||
* {@link JobRegistry.readRecord}, and snapshots carry
|
||||
* {@link JobSnapshot.outputTotal} and {@link JobSnapshot.outputEarliest}.
|
||||
* Without the declaration `append` logs and drops.
|
||||
*/
|
||||
record?: true
|
||||
run(job: RunningJob): JobHooks
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A start that declares an observable output record beside the model-facing
|
||||
* surfaces: {@link RecordingJob.append} retains chunks in a bounded ring that
|
||||
* any number of observers read at absolute byte offsets through
|
||||
* {@link JobRegistry.readRecord}, and snapshots carry
|
||||
* {@link JobSnapshot.outputTotal} and {@link JobSnapshot.outputEarliest}.
|
||||
*/
|
||||
interface RecordingJobStart extends JobStartBase {
|
||||
record: true
|
||||
/**
|
||||
* Start the work after preflight and synchronously return its hooks. Called
|
||||
* once with the job's producer face; a throw leaves nothing registered (the
|
||||
@@ -66,10 +61,20 @@ interface JobStart {
|
||||
* started resources.
|
||||
* @param job - the issued id plus the record append and live-detail writers.
|
||||
*/
|
||||
run(job: RunningJob): JobHooks
|
||||
run(job: RecordingJob): JobHooks
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Producer declaration passed to {@link JobRegistry.start}, discriminated by
|
||||
* `record`. The runtime preflights access and cleanup before invoking `run`;
|
||||
* the producer owns execution resources while the runtime owns identity and
|
||||
* lifecycle state.
|
||||
*/
|
||||
type JobStart = PlainJobStart | RecordingJobStart
|
||||
```
|
||||
|
||||
`JobHooks.done` resolves after the producer releases its resources, not merely when work finishes. Optional `readOutput` distinguishes consuming stream jobs from final-output-only jobs.
|
||||
|
||||
```ts type-equiv
|
||||
@@ -113,34 +118,38 @@ interface JobOutcome {
|
||||
|
||||
## Observation record
|
||||
|
||||
A producer that declares `record: true` streams raw output into a bounded per-job ring through the `RunningJob` face its starter receives; any number of observers read retained chunks at absolute byte offsets through `readRecord` without touching the consuming model cursor or notice state. Job settlement ends the stream and trims retention to the settled cap — the record has no separate lifecycle. `pumpJobOutput` copies producer offset-readers (the subprocess `readFrom` family) into the record at a bounded cadence. Browsers reach the record through `job.observe`, the Remote stream of [`dsh-api-job-controller`](../../packages/api/job-controller/README.md), whose frames are listed under its Cordis API section below.
|
||||
A producer that declares `record: true` streams raw output into a bounded per-job ring through the `RecordingJob` face its starter receives; any number of observers read retained chunks at absolute byte offsets through `readRecord` without touching the consuming model cursor or notice state. Job settlement ends the stream and trims retention to the settled cap — the record has no separate lifecycle. `pumpJobOutput` copies producer offset-readers (the subprocess `readFrom` family) into the record at a bounded cadence. Browsers reach the record through `job.observe`, the Remote stream of [`dsh-api-job-controller`](../../packages/api/job-controller/README.md), whose frames are listed under its Cordis API section below.
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Producer face of one registered job, handed to {@link JobStart.run} and
|
||||
* valid for the job's whole life. All methods are synchronous. Writes staged
|
||||
* inside the starter call are retained and become visible with the
|
||||
* registration commit; after settlement — the producer's own outcome, a kill,
|
||||
* or a registry-forced teardown end — both methods log and drop instead of
|
||||
* throwing, so a producer's trailing flush cannot break its own teardown path.
|
||||
* Producer face of one registered job, handed to `run` and valid for the
|
||||
* job's whole life. All methods are synchronous. Writes staged inside the
|
||||
* starter call are retained and become visible with the registration commit;
|
||||
* after settlement — the producer's own outcome, a kill, or a registry-forced
|
||||
* teardown end — writes log and drop instead of throwing, so a producer's
|
||||
* trailing flush cannot break its own teardown path.
|
||||
*/
|
||||
interface RunningJob {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* Replace the snapshot's status detail with a live progress line (`3/10`).
|
||||
* @param detail - the new detail line.
|
||||
*/
|
||||
updateDetail(detail: string): void
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Producer face of a {@link RecordingJobStart}: {@link RunningJob} plus the record append. */
|
||||
interface RecordingJob extends RunningJob {
|
||||
/**
|
||||
* Append one record chunk. Offsets advance by the chunk's UTF-8 byte
|
||||
* length; an empty chunk is dropped without waking observers. Without a
|
||||
* {@link JobStart.record} declaration the chunk is logged and dropped.
|
||||
* length; an empty chunk is dropped without waking observers.
|
||||
* @param text - the chunk text, exactly as produced.
|
||||
* @param options - stream label and gap marker.
|
||||
*/
|
||||
append(text: string, options?: JobAppendOptions): void
|
||||
/**
|
||||
* Replace the snapshot's status detail with a live progress line (`3/10`).
|
||||
* Works for every job, with or without a record.
|
||||
* @param detail - the new detail line.
|
||||
*/
|
||||
updateDetail(detail: string): void
|
||||
}
|
||||
```
|
||||
|
||||
@@ -368,7 +377,6 @@ abstract wait(id: JobId, timeoutMs: number, caller?: Agent, signal?: AbortSignal
|
||||
*/
|
||||
abstract onJobDone(listener: JobDoneListener): () => void
|
||||
|
||||
/**
|
||||
/**
|
||||
* Register an effect-scoped observer of visible-set changes. It fires after
|
||||
* every commit that changes what {@link list} returns for that owner —
|
||||
|
||||
+55
-47
@@ -23,42 +23,37 @@ interface JobKindMap {
|
||||
|
||||
## 生产方约定
|
||||
|
||||
`JobStart` 声明身份和启动器。运行时会在完成预检后携带该 job 的生产者面调用 `run()`,随后提交注册,不再执行可能失败的步骤。生产方拥有执行资源;运行时拥有身份、访问权限和生命周期状态。
|
||||
`JobStart` 声明身份和启动器,并按 `record` 判别:`RecordingJobStart` 收到 `RecordingJob` 面,`PlainJobStart` 收到没有 `append` 的 `RunningJob` 面。运行时会在完成预检后携带该面调用 `run()`,随后提交注册,不再执行可能失败的步骤。生产方拥有执行资源;运行时拥有身份、访问权限和生命周期状态。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Producer declaration passed to {@link JobRegistry.start}. The runtime
|
||||
* preflights access and cleanup before invoking {@link run}; the producer owns
|
||||
* execution resources while the runtime owns identity and lifecycle state.
|
||||
* A start with the model-facing surfaces only. Its producer face carries no
|
||||
* `append`, so output the registry would have nowhere to keep is
|
||||
* unrepresentable rather than dropped.
|
||||
*/
|
||||
interface JobStart {
|
||||
/** Producer kind — also the id prefix (`bash`, `subagent`, …). */
|
||||
kind: JobKind
|
||||
/** One-line model-facing label (the command; the delegation description). */
|
||||
label: string
|
||||
interface PlainJobStart extends JobStartBase {
|
||||
record?: undefined
|
||||
/**
|
||||
* Optional UTF-8 byte cap for each complete model-facing completion notice or
|
||||
* output read, including controller status metadata. Independent of record
|
||||
* retention: it bounds the consuming model surface, never
|
||||
* {@link JobRegistry.readRecord}.
|
||||
* Start the work after preflight and synchronously return its hooks. Called
|
||||
* once with the job's producer face; a throw leaves nothing registered (the
|
||||
* spent ordinal is skipped), and the producer must clean up any partially
|
||||
* started resources.
|
||||
* @param job - the issued id plus the live-detail writer.
|
||||
*/
|
||||
outputLimitBytes?: number
|
||||
/**
|
||||
* Owning live agent. Access is fenced by its session id, and agent disposal
|
||||
* cancels and awaits the job. The instance must be the one currently
|
||||
* registered under its agent id. Omitting the owner creates an unowned job,
|
||||
* open to any caller until service disposal.
|
||||
*/
|
||||
owner?: Agent
|
||||
/**
|
||||
* Declares an observable output record beside the model-facing surfaces:
|
||||
* {@link RunningJob.append} retains chunks in a bounded ring that any number
|
||||
* of observers read at absolute byte offsets through
|
||||
* {@link JobRegistry.readRecord}, and snapshots carry
|
||||
* {@link JobSnapshot.outputTotal} and {@link JobSnapshot.outputEarliest}.
|
||||
* Without the declaration `append` logs and drops.
|
||||
*/
|
||||
record?: true
|
||||
run(job: RunningJob): JobHooks
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* A start that declares an observable output record beside the model-facing
|
||||
* surfaces: {@link RecordingJob.append} retains chunks in a bounded ring that
|
||||
* any number of observers read at absolute byte offsets through
|
||||
* {@link JobRegistry.readRecord}, and snapshots carry
|
||||
* {@link JobSnapshot.outputTotal} and {@link JobSnapshot.outputEarliest}.
|
||||
*/
|
||||
interface RecordingJobStart extends JobStartBase {
|
||||
record: true
|
||||
/**
|
||||
* Start the work after preflight and synchronously return its hooks. Called
|
||||
* once with the job's producer face; a throw leaves nothing registered (the
|
||||
@@ -66,10 +61,20 @@ interface JobStart {
|
||||
* started resources.
|
||||
* @param job - the issued id plus the record append and live-detail writers.
|
||||
*/
|
||||
run(job: RunningJob): JobHooks
|
||||
run(job: RecordingJob): JobHooks
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Producer declaration passed to {@link JobRegistry.start}, discriminated by
|
||||
* `record`. The runtime preflights access and cleanup before invoking `run`;
|
||||
* the producer owns execution resources while the runtime owns identity and
|
||||
* lifecycle state.
|
||||
*/
|
||||
type JobStart = PlainJobStart | RecordingJobStart
|
||||
```
|
||||
|
||||
`JobHooks.done` 会在生产方释放其资源后 resolve,而不是仅在工作完成时 resolve。可选的 `readOutput` 用来区分会消费输出的流式任务和仅有最终输出的任务。
|
||||
|
||||
```ts type-equiv
|
||||
@@ -113,34 +118,38 @@ interface JobOutcome {
|
||||
|
||||
## 观测 record
|
||||
|
||||
声明 `record: true` 的生产方通过 starter 收到的 `RunningJob` 面把原始输出流入按 job 划分的有界环形缓冲;任意数量的观察者通过 `readRecord` 按绝对字节偏移读取保留块,不触碰消耗型的模型游标与通知状态。job 结算即封流并把保留量裁剪到结算上限——record 没有独立生命周期。`pumpJobOutput` 以有界节奏把生产方的偏移读取器(subprocess 的 `readFrom` 家族)复制进 record。浏览器经 [`dsh-api-job-controller`](../../packages/api/job-controller/README.zh.md) 的 Remote 流 `job.observe` 读取 record,其帧类型列在下方该控制器的 Cordis API 一节。
|
||||
声明 `record: true` 的生产方通过 starter 收到的 `RecordingJob` 面把原始输出流入按 job 划分的有界环形缓冲;任意数量的观察者通过 `readRecord` 按绝对字节偏移读取保留块,不触碰消耗型的模型游标与通知状态。job 结算即封流并把保留量裁剪到结算上限——record 没有独立生命周期。`pumpJobOutput` 以有界节奏把生产方的偏移读取器(subprocess 的 `readFrom` 家族)复制进 record。浏览器经 [`dsh-api-job-controller`](../../packages/api/job-controller/README.zh.md) 的 Remote 流 `job.observe` 读取 record,其帧类型列在下方该控制器的 Cordis API 一节。
|
||||
|
||||
```ts type-equiv
|
||||
/**
|
||||
* Producer face of one registered job, handed to {@link JobStart.run} and
|
||||
* valid for the job's whole life. All methods are synchronous. Writes staged
|
||||
* inside the starter call are retained and become visible with the
|
||||
* registration commit; after settlement — the producer's own outcome, a kill,
|
||||
* or a registry-forced teardown end — both methods log and drop instead of
|
||||
* throwing, so a producer's trailing flush cannot break its own teardown path.
|
||||
* Producer face of one registered job, handed to `run` and valid for the
|
||||
* job's whole life. All methods are synchronous. Writes staged inside the
|
||||
* starter call are retained and become visible with the registration commit;
|
||||
* after settlement — the producer's own outcome, a kill, or a registry-forced
|
||||
* teardown end — writes log and drop instead of throwing, so a producer's
|
||||
* trailing flush cannot break its own teardown path.
|
||||
*/
|
||||
interface RunningJob {
|
||||
/** The registry-issued id (`<kind>-N`). */
|
||||
readonly id: JobId
|
||||
/**
|
||||
* Replace the snapshot's status detail with a live progress line (`3/10`).
|
||||
* @param detail - the new detail line.
|
||||
*/
|
||||
updateDetail(detail: string): void
|
||||
}
|
||||
```
|
||||
|
||||
```ts type-equiv
|
||||
/** Producer face of a {@link RecordingJobStart}: {@link RunningJob} plus the record append. */
|
||||
interface RecordingJob extends RunningJob {
|
||||
/**
|
||||
* Append one record chunk. Offsets advance by the chunk's UTF-8 byte
|
||||
* length; an empty chunk is dropped without waking observers. Without a
|
||||
* {@link JobStart.record} declaration the chunk is logged and dropped.
|
||||
* length; an empty chunk is dropped without waking observers.
|
||||
* @param text - the chunk text, exactly as produced.
|
||||
* @param options - stream label and gap marker.
|
||||
*/
|
||||
append(text: string, options?: JobAppendOptions): void
|
||||
/**
|
||||
* Replace the snapshot's status detail with a live progress line (`3/10`).
|
||||
* Works for every job, with or without a record.
|
||||
* @param detail - the new detail line.
|
||||
*/
|
||||
updateDetail(detail: string): void
|
||||
}
|
||||
```
|
||||
|
||||
@@ -368,7 +377,6 @@ abstract wait(id: JobId, timeoutMs: number, caller?: Agent, signal?: AbortSignal
|
||||
*/
|
||||
abstract onJobDone(listener: JobDoneListener): () => void
|
||||
|
||||
/**
|
||||
/**
|
||||
* Register an effect-scoped observer of visible-set changes. It fires after
|
||||
* every commit that changes what {@link list} returns for that owner —
|
||||
|
||||
Reference in new issue
Block a user