@deepseek-ai/dsh-jobs 0.0.1-rc.3
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 +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +40 -0
- package/README.zh.md +40 -0
- package/lib/index.js +65 -0
- package/lib/invariant.js +40 -0
- package/lib/types/brand.d.ts +24 -0
- package/lib/types/brand.js +20 -0
- package/lib/types/index.d.ts +145 -0
- package/lib/types/index.js +49 -0
- package/lib/types/invariant.d.ts +13 -0
- package/lib/types/invariant.js +47 -0
- package/lib/types/types.d.ts +146 -0
- package/lib/types/types.js +7 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/jobs/jobs/README.md
|
|
5
|
+
README.md: f4469286603f687be5789bd016ab462dba26e664
|
|
6
|
+
README.zh.md: 720c8b550b52716ef66d182da0acf81eae4490ca
|
package/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-jobs
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
The background job registry contract (`ctx.jobs`). The abstract `JobRegistry` and its vocabulary types give long-running producers shared ids, owner isolation, reads, cancellation, waiting, notices, and cleanup under one contract; the process-local registry lives in [`dsh-jobs-local`](../jobs-local/README.md). Producer plugins extend `JobKindMap` with their opaque id namespace.
|
|
6
|
+
|
|
7
|
+
## Service contract
|
|
8
|
+
|
|
9
|
+
- `start(spec): JobId` validates the attached controller, spec, exact live owner, optional positive `outputLimitBytes`, and any provider-owned admission policy before calling the producer's `run()` once. A preflight rejection or starter throw leaves no job id or registered work; successful return commits without another failable step.
|
|
10
|
+
- `get(id, caller?)` and `list(caller?)` return non-consuming snapshots. Listing includes only caller-owned and unowned jobs.
|
|
11
|
+
- `read(id, caller?)` consumes the single cursor for stream jobs and reads terminal output idempotently for final-output jobs.
|
|
12
|
+
- `kill(id, caller?, reason?)` invokes producer cancellation before changing status. A cancellation throw leaves the job running; success changes it to `stopping` and marks terminal delivery reported.
|
|
13
|
+
- `wait(id, timeoutMs, caller?, signal?)` returns a terminal snapshot or the live snapshot at timeout. Aborting stops only the wait; settlement wins once it has committed terminal delivery to that waiter.
|
|
14
|
+
- `onJobDone(listener)` observes each terminal record with the exact owner. Listener throws and rejections are contained; listener work is not awaited.
|
|
15
|
+
- `onJobsChanged(listener)` observes visible-set changes — registration, every stopping transition (teardown's included, before it awaits a slow producer), settlement, owner-disposal removal, and the emptying service disposal commits — carrying only the owner whose set moved, or `undefined` when an unowned job changed and every caller's set moved with it. It is owner-granular because removal is a change no per-job record can express, and it is not a superset of `onJobDone`: it carries no delivery meaning and marks nothing reported. The registration binds to the calling fiber, so an observer mounted outside the registry still sees the disposal emptying.
|
|
16
|
+
- `attachController(name)` declares a job controller for its effect lifetime. `start()` fails before producer execution when no attached controller serves the spec's owner.
|
|
17
|
+
|
|
18
|
+
All three registrations are owner-relative, because one registry serves every composition in the process. A controller or listener registered from an unscoped context serves every owner; one registered under an agent composition's scope serves exactly the agents composed under it. So a composition that loads no controller cannot start background work on the strength of another composition's controls, and one settlement notifies only the listeners its owner's composition registered.
|
|
19
|
+
|
|
20
|
+
Owned access compares the job's `SessionId` with the caller's. Ids such as `bash-1` are predictable, so this fence is the boundary. Unowned jobs are open to callers and last until service disposal.
|
|
21
|
+
|
|
22
|
+
`outputLimitBytes` is producer-owned model-presentation policy carried unchanged into snapshots. A controller applies it after adding status or notice metadata; the registry does not rewrite producer output or invent a default for producers that omit it.
|
|
23
|
+
|
|
24
|
+
Implementations also owe the lifecycle semantics of the contract: registrations outlive producer and controller fibers, owner and service disposal cancel live work and await compliant producers, and settlement is first-wins — one terminal record, one round of contained listener notification, released waiters.
|
|
25
|
+
|
|
26
|
+
See the [job type catalog](../../../docs/subsystems/jobs.md), the [runtime Agent Note](../../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md), and the [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md).
|
|
27
|
+
|
|
28
|
+
## Model Experience
|
|
29
|
+
|
|
30
|
+
Indirectly, through producer plugins and [`dsh-tool-jobs`](../tool-jobs/README.md), which render job ids, output, status, cancellation, and completion notices.
|
|
31
|
+
|
|
32
|
+
#### KV Cache effect
|
|
33
|
+
|
|
34
|
+
No direct invalidation; the named consumer owns any request-prefix changes.
|
|
35
|
+
|
|
36
|
+
## Known Limitations and Deferred Work
|
|
37
|
+
|
|
38
|
+
- **Stream output has one consuming cursor** — independent observers need a cursor or snapshot API.
|
|
39
|
+
- **Foreground work cannot be promoted** — producers choose foreground or background before starting.
|
|
40
|
+
- **The contract is in-process** — `JobStart.run()` passes callbacks and exact `Agent` objects; a durable or cross-process backend must reshape identity, restart, ownership, and observation semantics before it can implement this seam.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-jobs
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
后台任务注册表约定(`ctx.jobs`)。抽象的 `JobRegistry` 及其词汇类型在同一份约定下为长时间运行的生产方提供共享 id、owner 隔离、读取、取消、等待、通知和清理;进程局部注册表位于 [`dsh-jobs-local`](../jobs-local/README.md)。生产方插件使用其不透明 id namespace 扩展 `JobKindMap`。
|
|
6
|
+
|
|
7
|
+
## 服务约定
|
|
8
|
+
|
|
9
|
+
- `start(spec): JobId` 验证已附加的任务控制器、spec、确切且仍存活的 owner、可选的正数 `outputLimitBytes`,以及 Service provider 所拥有的准入策略,然后只调用生产方的 `run()` 一次。预检拒绝或启动方抛出异常时都不会生成 job id 或注册工作;成功返回会直接提交,不再执行其他可能失败的步骤。
|
|
10
|
+
- `get(id, caller?)` 和 `list(caller?)` 返回非消费式快照。列表只包含调用方拥有及无 owner 的任务。
|
|
11
|
+
- `read(id, caller?)` 消费流任务的唯一游标;对于最终输出任务,则以幂等方式读取终止输出。
|
|
12
|
+
- `kill(id, caller?, reason?)` 在更改状态前调用生产方取消。取消抛出异常时任务保持运行;成功则把状态改为 `stopping`,并将终止交付标记为已报告。
|
|
13
|
+
- `wait(id, timeoutMs, caller?, signal?)` 返回终止快照,或在超时时返回存活快照。中止只会停止等待;一旦终止交付已向该等待方提交,终止结果优先。
|
|
14
|
+
- `onJobDone(listener)` 观察每条终止记录及其精确 owner。监听器抛出的异常和产生的拒绝都会被隔离;系统不会等待监听器工作。
|
|
15
|
+
- `onJobsChanged(listener)` 观察可见集合的变化——注册、每一次转入 stopping(包括 teardown 在等待缓慢生产者之前的那一次)、结算、owner 销毁时的移除,以及服务销毁提交的清空——只携带集合发生变化的那个 owner,或在无主任务变化、因而每个调用方的集合都随之变化时携带 `undefined`。它按 owner 分粒度,因为移除是任何逐任务记录都无法表达的变化;它也不是 `onJobDone` 的超集:它不含任何投递含义,也不把任何东西标为已上报。注册绑定的是调用方 fiber,因此挂在注册表之外的观察者仍能收到销毁时的清空。
|
|
16
|
+
- `attachController(name)` 在其 effect 生命周期内声明任务控制器。当没有任何已附加的控制器服务于 spec 的所有者时,`start()` 会在生产方执行前失败。
|
|
17
|
+
|
|
18
|
+
这三类注册都是相对于所有者的,因为一个注册表要服务进程内的每一套组合。从不带 scope 的上下文注册的控制器或监听器服务于每个所有者;在某套 agent 组合的 scope 下注册的,则恰好服务于在该组合下组合出的 agent。因此,未加载任何控制器的组合无法借另一套组合的控制工具启动后台工作,而一次结算也只会通知其所有者所属组合注册的监听器。
|
|
19
|
+
|
|
20
|
+
有 owner 的访问会比较任务的 `SessionId` 与调用方。`bash-1` 等 id 可预测,因此这道隔离是安全边界。无 owner 的任务向调用方开放,并持续到服务 dispose(资源释放)为止。
|
|
21
|
+
|
|
22
|
+
`outputLimitBytes` 是生产方拥有的模型呈现策略,会原样携带到快照中。控制器在添加状态或通知元数据后应用它;注册表不会重写生产方输出,也不会为省略此字段的生产方虚构默认值。
|
|
23
|
+
|
|
24
|
+
实现还必须兑现约定的生命周期语义:注册的存续期长于生产方 fiber 与控制器 fiber,owner 释放和服务释放会取消仍在运行的工作并等待守约的生产方,结算遵循首次结果优先(一条终止记录、一轮异常受到隔离的监听器通知,然后释放等待方)。
|
|
25
|
+
|
|
26
|
+
参见[任务类型目录](../../../docs/subsystems/jobs.md)、[运行时 Agent Note](../../../.agents/notes/implemented/architecture/2026-06-20-generic-long-running-tool-runtime.md)和 [seam Agent Note](../../../.agents/notes/implemented/architecture/2026-07-26-job-registry-seam.md)。
|
|
27
|
+
|
|
28
|
+
## 模型体验
|
|
29
|
+
|
|
30
|
+
通过生产方插件和 [`dsh-tool-jobs`](../tool-jobs/README.md) 间接影响;它们会渲染 job id、输出、状态、取消和完成通知。
|
|
31
|
+
|
|
32
|
+
#### KV Cache 影响
|
|
33
|
+
|
|
34
|
+
不会直接导致 KV Cache 失效;请求前缀变更由上述消费方负责。
|
|
35
|
+
|
|
36
|
+
## 已知限制与暂缓事项
|
|
37
|
+
|
|
38
|
+
- **流输出只有一个消费游标**:独立观察者需要游标或快照 API。
|
|
39
|
+
- **前台工作无法转为后台**:生产方在启动前选择前台或后台。
|
|
40
|
+
- **约定是进程内的**:`JobStart.run()` 传入回调和确切的 `Agent` 对象;持久化或跨进程后端必须先重塑身份、重启、所有权与观察语义,才能实现此 seam。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
//#region lib/types/brand.js
|
|
3
|
+
/**
|
|
4
|
+
* dsh-jobs' owned branded id, carried across the registry, the model-facing
|
|
5
|
+
* control surface, and the client wire.
|
|
6
|
+
*
|
|
7
|
+
* It lives in its own leaf because the package root and `./types` both reach
|
|
8
|
+
* `dsh-agent` through the owner and listener signatures, which a Client program
|
|
9
|
+
* cannot resolve even as a type. A browser-safe consumer imports the id here;
|
|
10
|
+
* `Branded<B>` itself comes from the zero-dependency `@deepseek-ai/dsh-brand`.
|
|
11
|
+
*
|
|
12
|
+
* @module @deepseek-ai/dsh-jobs/brand
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Brand a string as a {@link JobId}.
|
|
16
|
+
* @param id - the raw job-id string (the registry generates `<kind>-N`).
|
|
17
|
+
* @returns the same string, branded; no validation is performed.
|
|
18
|
+
*/
|
|
19
|
+
function JobId(id) {
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region lib/types/index.js
|
|
24
|
+
/**
|
|
25
|
+
* The background-job Service Definition (`ctx.jobs`). It owns the contract for
|
|
26
|
+
* job ids, session-scoped access, lifecycle state, completion listeners, and
|
|
27
|
+
* owner cleanup while producers retain their execution resources. The
|
|
28
|
+
* process-local registry lives in `@deepseek-ai/dsh-jobs-local`.
|
|
29
|
+
* @module @deepseek-ai/dsh-jobs
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Abstract background job registry. Subclass, implement the abstract methods,
|
|
33
|
+
* and load the subclass as a plugin — it registers as `ctx.jobs` (one
|
|
34
|
+
* implementation per context; loading a second throws, which is cordis'
|
|
35
|
+
* standard duplicate-service behavior).
|
|
36
|
+
*
|
|
37
|
+
* Implementations must honor these semantics:
|
|
38
|
+
* - Registrations outlive producer and controller fibers. Owner and
|
|
39
|
+
* service disposal cancel live work and await compliant producers; a
|
|
40
|
+
* throwing teardown cancel force-fails only the record. Teardown
|
|
41
|
+
* cancellation also marks the record reported, because a record its owner
|
|
42
|
+
* is being destroyed for has no reader left.
|
|
43
|
+
* - Owned-job access is fenced by the owner's session id. Ids are
|
|
44
|
+
* predictable, so authorization — not secrecy — is the boundary.
|
|
45
|
+
* - Settlement is first-wins: one terminal record, released waiters, and one
|
|
46
|
+
* round of contained listener notification, even against a late producer
|
|
47
|
+
* outcome. Completion is announced last, after the record is committed and
|
|
48
|
+
* every other observer of the settlement has seen it, because a reporter
|
|
49
|
+
* may open a model turn synchronously.
|
|
50
|
+
* - {@link start} refuses work while no attached job controller serves the
|
|
51
|
+
* spec's owner, so a producer cannot start work that owner cannot collect
|
|
52
|
+
* or stop. One registry serves every composition in the process, so this
|
|
53
|
+
* question — and completion-listener delivery — is owner-relative rather
|
|
54
|
+
* than process-wide: registrations made from an unscoped context serve
|
|
55
|
+
* every owner, and registrations made under an agent composition's scope
|
|
56
|
+
* serve exactly the agents composed under it.
|
|
57
|
+
*/
|
|
58
|
+
var JobRegistry = class JobRegistry extends Service {
|
|
59
|
+
constructor(ctx) {
|
|
60
|
+
if (new.target === JobRegistry) throw new Error("@deepseek-ai/dsh-jobs is the abstract job registry seam; load an implementation such as @deepseek-ai/dsh-jobs-local instead");
|
|
61
|
+
super(ctx, "jobs");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
export { JobId, JobRegistry, JobRegistry as default };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/** Package-owned background-job snapshot invariants. @module @deepseek-ai/dsh-jobs/invariant */
|
|
3
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-jobs";
|
|
4
|
+
const TERMINAL_STATUSES = new Set([
|
|
5
|
+
"completed",
|
|
6
|
+
"killed",
|
|
7
|
+
"failed"
|
|
8
|
+
]);
|
|
9
|
+
/** Cordis companion plugin name. */
|
|
10
|
+
const name = "jobs-invariant";
|
|
11
|
+
/** Service required before the companion can reserve package ownership. */
|
|
12
|
+
const inject = ["invariants"];
|
|
13
|
+
/** Validate the cross-field relationships in one registry snapshot. */
|
|
14
|
+
function validateSnapshot(snapshot, owner, fail) {
|
|
15
|
+
const id = String(snapshot.id);
|
|
16
|
+
const prefix = `${snapshot.kind}-`;
|
|
17
|
+
const ordinal = Number(id.slice(prefix.length));
|
|
18
|
+
if (snapshot.kind.length === 0 || !id.startsWith(prefix) || !Number.isSafeInteger(ordinal) || ordinal < 1) fail(`job snapshot id ${JSON.stringify(id)} must be ${JSON.stringify(prefix)} followed by a positive ordinal`);
|
|
19
|
+
if (snapshot.label.length === 0) fail(`job ${JSON.stringify(id)} label must be non-empty`);
|
|
20
|
+
if (!Number.isSafeInteger(snapshot.startedAt) || snapshot.startedAt < 0) fail(`job ${JSON.stringify(id)} startedAt must be a non-negative epoch integer`);
|
|
21
|
+
if (TERMINAL_STATUSES.has(snapshot.status) !== (snapshot.finishedAt !== void 0)) fail(`job ${JSON.stringify(id)} finishedAt must be present exactly for a terminal status`);
|
|
22
|
+
if (snapshot.finishedAt !== void 0 && (!Number.isSafeInteger(snapshot.finishedAt) || snapshot.finishedAt < snapshot.startedAt)) fail(`job ${JSON.stringify(id)} finishedAt must be an epoch integer no earlier than startedAt`);
|
|
23
|
+
const expectedOwner = owner?.id;
|
|
24
|
+
if (snapshot.ownerSession !== expectedOwner) fail(`job ${JSON.stringify(id)} ownerSession does not match its completion owner`);
|
|
25
|
+
}
|
|
26
|
+
/** Install checks over current unowned records and every terminal snapshot. */
|
|
27
|
+
const install = Object.assign((ctx, fail) => {
|
|
28
|
+
for (const snapshot of ctx.jobs.list()) validateSnapshot(snapshot, void 0, fail);
|
|
29
|
+
ctx.jobs.onJobDone((snapshot, owner) => {
|
|
30
|
+
validateSnapshot(snapshot, owner, fail);
|
|
31
|
+
});
|
|
32
|
+
}, { inject: ["jobs"] });
|
|
33
|
+
/**
|
|
34
|
+
* Register the job-registry invariant companion.
|
|
35
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
36
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
37
|
+
*/
|
|
38
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
39
|
+
//#endregion
|
|
40
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-jobs' owned branded id, carried across the registry, the model-facing
|
|
3
|
+
* control surface, and the client wire.
|
|
4
|
+
*
|
|
5
|
+
* It lives in its own leaf because the package root and `./types` both reach
|
|
6
|
+
* `dsh-agent` through the owner and listener signatures, which a Client program
|
|
7
|
+
* cannot resolve even as a type. A browser-safe consumer imports the id here;
|
|
8
|
+
* `Branded<B>` itself comes from the zero-dependency `@deepseek-ai/dsh-brand`.
|
|
9
|
+
*
|
|
10
|
+
* @module @deepseek-ai/dsh-jobs/brand
|
|
11
|
+
*/
|
|
12
|
+
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
13
|
+
/**
|
|
14
|
+
* Identifies a background job. The registry generates `<kind>-N`; predictable
|
|
15
|
+
* ids rely on owner authorization rather than secrecy.
|
|
16
|
+
*/
|
|
17
|
+
export type JobId = Branded<'JobId'>;
|
|
18
|
+
/**
|
|
19
|
+
* Brand a string as a {@link JobId}.
|
|
20
|
+
* @param id - the raw job-id string (the registry generates `<kind>-N`).
|
|
21
|
+
* @returns the same string, branded; no validation is performed.
|
|
22
|
+
*/
|
|
23
|
+
export declare function JobId(id: string): JobId;
|
|
24
|
+
//# sourceMappingURL=brand.d.ts.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-jobs' owned branded id, carried across the registry, the model-facing
|
|
3
|
+
* control surface, and the client wire.
|
|
4
|
+
*
|
|
5
|
+
* It lives in its own leaf because the package root and `./types` both reach
|
|
6
|
+
* `dsh-agent` through the owner and listener signatures, which a Client program
|
|
7
|
+
* cannot resolve even as a type. A browser-safe consumer imports the id here;
|
|
8
|
+
* `Branded<B>` itself comes from the zero-dependency `@deepseek-ai/dsh-brand`.
|
|
9
|
+
*
|
|
10
|
+
* @module @deepseek-ai/dsh-jobs/brand
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Brand a string as a {@link JobId}.
|
|
14
|
+
* @param id - the raw job-id string (the registry generates `<kind>-N`).
|
|
15
|
+
* @returns the same string, branded; no validation is performed.
|
|
16
|
+
*/
|
|
17
|
+
export function JobId(id) {
|
|
18
|
+
return id;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=brand.js.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The background-job Service Definition (`ctx.jobs`). It owns the contract for
|
|
3
|
+
* job ids, session-scoped access, lifecycle state, completion listeners, and
|
|
4
|
+
* owner cleanup while producers retain their execution resources. The
|
|
5
|
+
* process-local registry lives in `@deepseek-ai/dsh-jobs-local`.
|
|
6
|
+
* @module @deepseek-ai/dsh-jobs
|
|
7
|
+
*/
|
|
8
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
9
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
10
|
+
import type { JobDoneListener, JobId, JobRead, JobSnapshot, JobStart, JobsChangedListener } from './types.ts';
|
|
11
|
+
export { JobId } from './types.ts';
|
|
12
|
+
export type { JobDoneListener, JobHooks, JobKind, JobKindMap, JobOutcome, JobRead, JobSnapshot, JobStart, JobStatus, JobsChangedListener, } from './types.ts';
|
|
13
|
+
declare module '@deepseek-ai/cordis' {
|
|
14
|
+
interface Context {
|
|
15
|
+
jobs: JobRegistry;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Abstract background job registry. Subclass, implement the abstract methods,
|
|
20
|
+
* and load the subclass as a plugin — it registers as `ctx.jobs` (one
|
|
21
|
+
* implementation per context; loading a second throws, which is cordis'
|
|
22
|
+
* standard duplicate-service behavior).
|
|
23
|
+
*
|
|
24
|
+
* Implementations must honor these semantics:
|
|
25
|
+
* - Registrations outlive producer and controller fibers. Owner and
|
|
26
|
+
* service disposal cancel live work and await compliant producers; a
|
|
27
|
+
* throwing teardown cancel force-fails only the record. Teardown
|
|
28
|
+
* cancellation also marks the record reported, because a record its owner
|
|
29
|
+
* is being destroyed for has no reader left.
|
|
30
|
+
* - Owned-job access is fenced by the owner's session id. Ids are
|
|
31
|
+
* predictable, so authorization — not secrecy — is the boundary.
|
|
32
|
+
* - Settlement is first-wins: one terminal record, released waiters, and one
|
|
33
|
+
* round of contained listener notification, even against a late producer
|
|
34
|
+
* outcome. Completion is announced last, after the record is committed and
|
|
35
|
+
* every other observer of the settlement has seen it, because a reporter
|
|
36
|
+
* may open a model turn synchronously.
|
|
37
|
+
* - {@link start} refuses work while no attached job controller serves the
|
|
38
|
+
* spec's owner, so a producer cannot start work that owner cannot collect
|
|
39
|
+
* or stop. One registry serves every composition in the process, so this
|
|
40
|
+
* question — and completion-listener delivery — is owner-relative rather
|
|
41
|
+
* than process-wide: registrations made from an unscoped context serve
|
|
42
|
+
* every owner, and registrations made under an agent composition's scope
|
|
43
|
+
* serve exactly the agents composed under it.
|
|
44
|
+
*/
|
|
45
|
+
export declare abstract class JobRegistry extends Service {
|
|
46
|
+
constructor(ctx: Context);
|
|
47
|
+
/**
|
|
48
|
+
* Preflight access, validation, owner cleanup, and implementation-owned
|
|
49
|
+
* admission before starting and atomically registering work. Any preflight
|
|
50
|
+
* rejection leaves no job id or execution resource. A throwing starter
|
|
51
|
+
* leaves nothing registered; after it returns, registration cannot fail.
|
|
52
|
+
* Settlement records the outcome, notifies listeners, and releases waiters.
|
|
53
|
+
* @param spec - job identity, owner, and synchronous starter.
|
|
54
|
+
* @returns the registry-issued `<kind>-N` id.
|
|
55
|
+
*/
|
|
56
|
+
abstract start(spec: JobStart): JobId;
|
|
57
|
+
/**
|
|
58
|
+
* List caller-owned and unowned jobs in registration order without exposing
|
|
59
|
+
* another session's labels.
|
|
60
|
+
* @param caller - reading agent; a non-agent caller sees only unowned jobs.
|
|
61
|
+
* @returns fresh snapshots.
|
|
62
|
+
*/
|
|
63
|
+
abstract list(caller?: Agent): JobSnapshot[];
|
|
64
|
+
/**
|
|
65
|
+
* Return a non-consuming snapshot without changing its read cursor or notice
|
|
66
|
+
* state. Throws for an unknown or foreign job.
|
|
67
|
+
* @param id - job to look up.
|
|
68
|
+
* @param caller - reading agent checked against the owner.
|
|
69
|
+
* @returns a fresh snapshot.
|
|
70
|
+
*/
|
|
71
|
+
abstract get(id: JobId, caller?: Agent): JobSnapshot;
|
|
72
|
+
/**
|
|
73
|
+
* Read the next stream delta, or the idempotent final output after settlement.
|
|
74
|
+
* A terminal read marks the job reported. Throws for an unknown or foreign
|
|
75
|
+
* job.
|
|
76
|
+
* @param id - job to read.
|
|
77
|
+
* @param caller - reading agent checked against the owner.
|
|
78
|
+
* @returns output text and the post-read snapshot.
|
|
79
|
+
*/
|
|
80
|
+
abstract read(id: JobId, caller?: Agent): JobRead;
|
|
81
|
+
/**
|
|
82
|
+
* Request cancellation, then mark the job stopping and reported. A producer
|
|
83
|
+
* throw propagates without changing job state. Throws for an unknown or
|
|
84
|
+
* foreign job.
|
|
85
|
+
* @param id - job to cancel.
|
|
86
|
+
* @param caller - killing agent checked against the owner.
|
|
87
|
+
* @param reason - logged reason forwarded to the producer.
|
|
88
|
+
* @returns `requested` for live work, otherwise `already-finished`.
|
|
89
|
+
*/
|
|
90
|
+
abstract kill(id: JobId, caller?: Agent, reason?: string): 'requested' | 'already-finished';
|
|
91
|
+
/**
|
|
92
|
+
* Wait for settlement or timeout without cancelling the job. Caller abort
|
|
93
|
+
* rejects only while the job is live; after settlement the terminal
|
|
94
|
+
* snapshot wins so a notice suppressed for this waiter is still delivered.
|
|
95
|
+
* Throws for invalid, unknown, or foreign input.
|
|
96
|
+
* @param id - job to wait for.
|
|
97
|
+
* @param timeoutMs - positive finite wait bound in milliseconds.
|
|
98
|
+
* @param caller - waiting agent checked against the owner.
|
|
99
|
+
* @param signal - optional cancellation of the wait itself.
|
|
100
|
+
* @returns snapshot at settlement or timeout.
|
|
101
|
+
*/
|
|
102
|
+
abstract wait(id: JobId, timeoutMs: number, caller?: Agent, signal?: AbortSignal): Promise<JobSnapshot>;
|
|
103
|
+
/**
|
|
104
|
+
* Register an effect-scoped completion listener. It receives the settlements
|
|
105
|
+
* of the owners its registering context's scope covers; each listener is
|
|
106
|
+
* contained; returned promises are observed but not awaited. No listener runs
|
|
107
|
+
* after service disposal.
|
|
108
|
+
* @param listener - receives each terminal snapshot and its exact owner.
|
|
109
|
+
* @returns disposer that unregisters the listener.
|
|
110
|
+
*/
|
|
111
|
+
abstract onJobDone(listener: JobDoneListener): () => void;
|
|
112
|
+
/**
|
|
113
|
+
/**
|
|
114
|
+
* Register an effect-scoped observer of visible-set changes. It fires after
|
|
115
|
+
* every commit that changes what {@link list} returns for that owner —
|
|
116
|
+
* registration, every stopping transition (including the one teardown
|
|
117
|
+
* performs before it awaits a slow producer), settlement, owner-disposal
|
|
118
|
+
* removal, and the emptying that service disposal commits — so an observer
|
|
119
|
+
* re-reads rather than accumulating deltas.
|
|
120
|
+
*
|
|
121
|
+
* Delivery is owner-relative on the same terms as {@link onJobDone}: an
|
|
122
|
+
* observer registered from an unscoped context — a host composition's own
|
|
123
|
+
* carrier — sees every owner, while one registered under an agent
|
|
124
|
+
* composition's scope sees exactly the agents composed under it.
|
|
125
|
+
*
|
|
126
|
+
* This is not a superset of {@link onJobDone}: that one delivers the terminal
|
|
127
|
+
* record under first-wins semantics a job controller couples to notice
|
|
128
|
+
* delivery, while this one carries no delivery meaning and marks nothing
|
|
129
|
+
* reported. Listeners are contained and never awaited.
|
|
130
|
+
* @param listener - receives the owner whose visible set changed, or
|
|
131
|
+
* `undefined` when an unowned job changed and every caller's set did.
|
|
132
|
+
* @returns disposer that unregisters the listener.
|
|
133
|
+
*/
|
|
134
|
+
abstract onJobsChanged(listener: JobsChangedListener): () => void;
|
|
135
|
+
/**
|
|
136
|
+
* Attach an effect-scoped controller that can read and stop jobs. It serves the
|
|
137
|
+
* owners its registering context's scope covers, and {@link start} refuses an
|
|
138
|
+
* owner no attached controller serves.
|
|
139
|
+
* @param name - diagnostic label; duplicate names remain independent.
|
|
140
|
+
* @returns disposer that detaches this controller.
|
|
141
|
+
*/
|
|
142
|
+
abstract attachController(name: string): () => void;
|
|
143
|
+
}
|
|
144
|
+
export default JobRegistry;
|
|
145
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The background-job Service Definition (`ctx.jobs`). It owns the contract for
|
|
3
|
+
* job ids, session-scoped access, lifecycle state, completion listeners, and
|
|
4
|
+
* owner cleanup while producers retain their execution resources. The
|
|
5
|
+
* process-local registry lives in `@deepseek-ai/dsh-jobs-local`.
|
|
6
|
+
* @module @deepseek-ai/dsh-jobs
|
|
7
|
+
*/
|
|
8
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
9
|
+
export { JobId } from "./types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Abstract background job registry. Subclass, implement the abstract methods,
|
|
12
|
+
* and load the subclass as a plugin — it registers as `ctx.jobs` (one
|
|
13
|
+
* implementation per context; loading a second throws, which is cordis'
|
|
14
|
+
* standard duplicate-service behavior).
|
|
15
|
+
*
|
|
16
|
+
* Implementations must honor these semantics:
|
|
17
|
+
* - Registrations outlive producer and controller fibers. Owner and
|
|
18
|
+
* service disposal cancel live work and await compliant producers; a
|
|
19
|
+
* throwing teardown cancel force-fails only the record. Teardown
|
|
20
|
+
* cancellation also marks the record reported, because a record its owner
|
|
21
|
+
* is being destroyed for has no reader left.
|
|
22
|
+
* - Owned-job access is fenced by the owner's session id. Ids are
|
|
23
|
+
* predictable, so authorization — not secrecy — is the boundary.
|
|
24
|
+
* - Settlement is first-wins: one terminal record, released waiters, and one
|
|
25
|
+
* round of contained listener notification, even against a late producer
|
|
26
|
+
* outcome. Completion is announced last, after the record is committed and
|
|
27
|
+
* every other observer of the settlement has seen it, because a reporter
|
|
28
|
+
* may open a model turn synchronously.
|
|
29
|
+
* - {@link start} refuses work while no attached job controller serves the
|
|
30
|
+
* spec's owner, so a producer cannot start work that owner cannot collect
|
|
31
|
+
* or stop. One registry serves every composition in the process, so this
|
|
32
|
+
* question — and completion-listener delivery — is owner-relative rather
|
|
33
|
+
* than process-wide: registrations made from an unscoped context serve
|
|
34
|
+
* every owner, and registrations made under an agent composition's scope
|
|
35
|
+
* serve exactly the agents composed under it.
|
|
36
|
+
*/
|
|
37
|
+
export class JobRegistry extends Service {
|
|
38
|
+
constructor(ctx) {
|
|
39
|
+
// `abstract` erases at runtime, so a composition row naming this package
|
|
40
|
+
// would register a ctx.jobs with no method implementations and fail far
|
|
41
|
+
// from the misconfiguration. Fail loud at load instead.
|
|
42
|
+
if (new.target === JobRegistry) {
|
|
43
|
+
throw new Error('@deepseek-ai/dsh-jobs is the abstract job registry seam; load an implementation such as @deepseek-ai/dsh-jobs-local instead');
|
|
44
|
+
}
|
|
45
|
+
super(ctx, 'jobs');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export default JobRegistry;
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Package-owned background-job snapshot invariants. @module @deepseek-ai/dsh-jobs/invariant */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
/** Cordis companion plugin name. */
|
|
4
|
+
export declare const name = "jobs-invariant";
|
|
5
|
+
/** Service required before the companion can reserve package ownership. */
|
|
6
|
+
export declare const inject: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Register the job-registry invariant companion.
|
|
9
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
10
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
11
|
+
*/
|
|
12
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
13
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Package-owned background-job snapshot invariants. @module @deepseek-ai/dsh-jobs/invariant */
|
|
2
|
+
const PACKAGE_NAME = '@deepseek-ai/dsh-jobs';
|
|
3
|
+
const TERMINAL_STATUSES = new Set(['completed', 'killed', 'failed']);
|
|
4
|
+
/** Cordis companion plugin name. */
|
|
5
|
+
export const name = 'jobs-invariant';
|
|
6
|
+
/** Service required before the companion can reserve package ownership. */
|
|
7
|
+
export const inject = ['invariants'];
|
|
8
|
+
/** Validate the cross-field relationships in one registry snapshot. */
|
|
9
|
+
function validateSnapshot(snapshot, owner, fail) {
|
|
10
|
+
const id = String(snapshot.id);
|
|
11
|
+
const prefix = `${snapshot.kind}-`;
|
|
12
|
+
const ordinal = Number(id.slice(prefix.length));
|
|
13
|
+
if (snapshot.kind.length === 0 || !id.startsWith(prefix)
|
|
14
|
+
|| !Number.isSafeInteger(ordinal) || ordinal < 1) {
|
|
15
|
+
fail(`job snapshot id ${JSON.stringify(id)} must be ${JSON.stringify(prefix)} followed by a positive ordinal`);
|
|
16
|
+
}
|
|
17
|
+
if (snapshot.label.length === 0)
|
|
18
|
+
fail(`job ${JSON.stringify(id)} label must be non-empty`);
|
|
19
|
+
if (!Number.isSafeInteger(snapshot.startedAt) || snapshot.startedAt < 0) {
|
|
20
|
+
fail(`job ${JSON.stringify(id)} startedAt must be a non-negative epoch integer`);
|
|
21
|
+
}
|
|
22
|
+
const terminal = TERMINAL_STATUSES.has(snapshot.status);
|
|
23
|
+
if (terminal !== (snapshot.finishedAt !== undefined)) {
|
|
24
|
+
fail(`job ${JSON.stringify(id)} finishedAt must be present exactly for a terminal status`);
|
|
25
|
+
}
|
|
26
|
+
if (snapshot.finishedAt !== undefined
|
|
27
|
+
&& (!Number.isSafeInteger(snapshot.finishedAt) || snapshot.finishedAt < snapshot.startedAt)) {
|
|
28
|
+
fail(`job ${JSON.stringify(id)} finishedAt must be an epoch integer no earlier than startedAt`);
|
|
29
|
+
}
|
|
30
|
+
const expectedOwner = owner?.id;
|
|
31
|
+
if (snapshot.ownerSession !== expectedOwner) {
|
|
32
|
+
fail(`job ${JSON.stringify(id)} ownerSession does not match its completion owner`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Install checks over current unowned records and every terminal snapshot. */
|
|
36
|
+
const install = Object.assign((ctx, fail) => {
|
|
37
|
+
for (const snapshot of ctx.jobs.list())
|
|
38
|
+
validateSnapshot(snapshot, undefined, fail);
|
|
39
|
+
ctx.jobs.onJobDone((snapshot, owner) => { validateSnapshot(snapshot, owner, fail); });
|
|
40
|
+
}, { inject: ['jobs'] });
|
|
41
|
+
/**
|
|
42
|
+
* Register the job-registry invariant companion.
|
|
43
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
44
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
45
|
+
*/
|
|
46
|
+
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
47
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types shared by job producers, the registry, and controllers. The
|
|
3
|
+
* service implementation lives in `./index.ts`.
|
|
4
|
+
* @module @deepseek-ai/dsh-jobs/types
|
|
5
|
+
*/
|
|
6
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
7
|
+
import type { SessionId } from '@deepseek-ai/dsh-session';
|
|
8
|
+
import type { JobId } from './brand.ts';
|
|
9
|
+
export { JobId } from './brand.ts';
|
|
10
|
+
/**
|
|
11
|
+
* Task lifecycle: `running`, optionally `stopping`, then exactly one terminal
|
|
12
|
+
* status. Producer-specific facts belong in {@link JobSnapshot.detail}.
|
|
13
|
+
*/
|
|
14
|
+
export type JobStatus = 'running' | 'stopping' | 'completed' | 'killed' | 'failed';
|
|
15
|
+
/**
|
|
16
|
+
* Producer-defined job kinds. Plugins extend this map by declaration merging;
|
|
17
|
+
* the registry treats every value as an opaque id namespace.
|
|
18
|
+
*/
|
|
19
|
+
export interface JobKindMap {
|
|
20
|
+
bash: 'bash';
|
|
21
|
+
subagent: 'subagent';
|
|
22
|
+
}
|
|
23
|
+
/** The merge-extensible union of registered producer kind names. */
|
|
24
|
+
export type JobKind = JobKindMap[keyof JobKindMap];
|
|
25
|
+
/** Terminal result supplied by a producer through {@link JobHooks.done}. */
|
|
26
|
+
export interface JobOutcome {
|
|
27
|
+
/** How the job ended: finished (`completed`), cancelled (`killed`), or broke (`failed`). */
|
|
28
|
+
status: 'completed' | 'killed' | 'failed';
|
|
29
|
+
/** Kind-specific detail rendered into status lines ('exit code: 3', 'max-tokens'). */
|
|
30
|
+
detail?: string;
|
|
31
|
+
/** Final output for jobs without `readOutput`; stream jobs leave it unset. */
|
|
32
|
+
output?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Producer declaration passed to {@link JobRegistry.start}. The runtime
|
|
36
|
+
* preflights access and cleanup before invoking {@link run}; the producer owns
|
|
37
|
+
* execution resources while the runtime owns identity and lifecycle state.
|
|
38
|
+
*/
|
|
39
|
+
export interface JobStart {
|
|
40
|
+
/** Producer kind — also the id prefix (`bash`, `subagent`, …). */
|
|
41
|
+
kind: JobKind;
|
|
42
|
+
/** One-line model-facing label (the command; the delegation description). */
|
|
43
|
+
label: string;
|
|
44
|
+
/**
|
|
45
|
+
* Optional UTF-8 byte cap for each complete model-facing completion notice or
|
|
46
|
+
* output read, including controller status metadata.
|
|
47
|
+
*/
|
|
48
|
+
outputLimitBytes?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Owning live agent. Access is fenced by its session id, and agent disposal
|
|
51
|
+
* cancels and awaits the job. The instance must be the one currently
|
|
52
|
+
* registered under its agent id. Omitting the owner creates an unowned job,
|
|
53
|
+
* open to any caller until service disposal.
|
|
54
|
+
*/
|
|
55
|
+
owner?: Agent;
|
|
56
|
+
/**
|
|
57
|
+
* Start the work after preflight and synchronously return its hooks. Called
|
|
58
|
+
* once; a throw leaves nothing registered, and the producer must clean up any
|
|
59
|
+
* partially started resources.
|
|
60
|
+
*/
|
|
61
|
+
run(): JobHooks;
|
|
62
|
+
}
|
|
63
|
+
/** Hooks through which the runtime controls and observes producer work. */
|
|
64
|
+
export interface JobHooks {
|
|
65
|
+
/**
|
|
66
|
+
* Request termination. Must be synchronous, idempotent, and eventually settle
|
|
67
|
+
* {@link done}; throws propagate. The optional reason is forwarded verbatim.
|
|
68
|
+
*/
|
|
69
|
+
cancel(reason?: string): void;
|
|
70
|
+
/**
|
|
71
|
+
* Resolves after the producer releases its resources, not merely when work
|
|
72
|
+
* finishes. Must not reject; the runtime converts a rejection to `failed`.
|
|
73
|
+
* If teardown cancellation throws, the runtime may force-fail only the
|
|
74
|
+
* registry record without claiming that the work stopped.
|
|
75
|
+
*/
|
|
76
|
+
done: Promise<JobOutcome>;
|
|
77
|
+
/**
|
|
78
|
+
* Consume output produced since the previous call. The producer formats
|
|
79
|
+
* truncation and spill notices. Absence marks a final-output-only job; each
|
|
80
|
+
* job has one consuming cursor.
|
|
81
|
+
*/
|
|
82
|
+
readOutput?(): string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A read-only projection of one job, safe to hand to listeners and tools —
|
|
86
|
+
* a fresh object per call, never live registry state.
|
|
87
|
+
*/
|
|
88
|
+
export interface JobSnapshot {
|
|
89
|
+
/** The registry-issued id (`<kind>-N`). */
|
|
90
|
+
id: JobId;
|
|
91
|
+
/** The producer kind the job was registered with. */
|
|
92
|
+
kind: JobKind;
|
|
93
|
+
/** The producer-supplied one-line label. */
|
|
94
|
+
label: string;
|
|
95
|
+
/** Producer-owned cap for complete model-facing notices and output reads. */
|
|
96
|
+
outputLimitBytes?: number;
|
|
97
|
+
/**
|
|
98
|
+
* Owner session id used for authorization and correlation; absent for
|
|
99
|
+
* unowned jobs. Completion listeners receive the exact {@link Agent}
|
|
100
|
+
* separately through {@link JobDoneListener}.
|
|
101
|
+
*/
|
|
102
|
+
ownerSession?: SessionId;
|
|
103
|
+
/** Current lifecycle state. */
|
|
104
|
+
status: JobStatus;
|
|
105
|
+
/** Kind-specific status detail, present once the producer supplied one (usually terminal). */
|
|
106
|
+
detail?: string;
|
|
107
|
+
/** Epoch ms when the job was registered. */
|
|
108
|
+
startedAt: number;
|
|
109
|
+
/** Epoch ms when the job settled; absent while `running`/`stopping`. */
|
|
110
|
+
finishedAt?: number;
|
|
111
|
+
/**
|
|
112
|
+
* True when a kill, read, wait, or teardown cancel has reported or committed
|
|
113
|
+
* to report the terminal state. Completion reporters suppress redundant
|
|
114
|
+
* notices when set. Teardown claims it because the owner or service being
|
|
115
|
+
* destroyed leaves no reader: a reporter that opens a turn on notice would
|
|
116
|
+
* otherwise spend a model request per teardown layer.
|
|
117
|
+
*/
|
|
118
|
+
reported: boolean;
|
|
119
|
+
}
|
|
120
|
+
/** Output and post-read state returned by {@link JobRegistry.read}. */
|
|
121
|
+
export interface JobRead {
|
|
122
|
+
/**
|
|
123
|
+
* Stream kinds: the consuming delta since the previous read. Final-output
|
|
124
|
+
* kinds: empty while live, the terminal {@link JobOutcome.output} (or
|
|
125
|
+
* empty) once settled — idempotent, never consumed.
|
|
126
|
+
*/
|
|
127
|
+
text: string;
|
|
128
|
+
/** The job's state at read time. */
|
|
129
|
+
snapshot: JobSnapshot;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Completion callback with the exact owner supplied at start, or `undefined`
|
|
133
|
+
* for an unowned job. Returned promises are observed but not awaited.
|
|
134
|
+
*/
|
|
135
|
+
export type JobDoneListener = (snapshot: JobSnapshot, owner: Agent | undefined) => void | PromiseLike<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Observation callback for a change to what one owner's {@link JobRegistry.list}
|
|
138
|
+
* would return. It is owner-granular rather than job-granular because the
|
|
139
|
+
* change may be a removal, which no per-job record can express, and because
|
|
140
|
+
* its consumers re-read the whole visible set anyway.
|
|
141
|
+
*
|
|
142
|
+
* An `undefined` owner means an unowned job changed, so every caller's visible
|
|
143
|
+
* set changed with it.
|
|
144
|
+
*/
|
|
145
|
+
export type JobsChangedListener = (owner: Agent | undefined) => void;
|
|
146
|
+
//# sourceMappingURL=types.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-jobs",
|
|
3
|
+
"description": "Background job registry (ctx.jobs) for the DeepSeek Harness — shared ids, owner isolation, polling, cancellation, and completion listeners for long-running tool work",
|
|
4
|
+
"version": "0.0.1-rc.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/jobs/jobs"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./brand": {
|
|
26
|
+
"types": "./lib/types/brand.d.ts",
|
|
27
|
+
"default": "./lib/types/brand.js"
|
|
28
|
+
},
|
|
29
|
+
"./src/*": "./src/*",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"lib/index.js",
|
|
34
|
+
"lib/invariant.js",
|
|
35
|
+
"lib/types/**/*.js",
|
|
36
|
+
"lib/types/**/*.d.ts"
|
|
37
|
+
],
|
|
38
|
+
"license": "BSD-3-Clause",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.3",
|
|
41
|
+
"@deepseek-ai/dsh-brand": "^0.0.1-rc.3",
|
|
42
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
|
|
43
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.3",
|
|
44
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.3",
|
|
48
|
+
"@deepseek-ai/dsh-brand": "^0.0.1-rc.3",
|
|
49
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
|
|
50
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-session": "^0.0.1-rc.3"
|
|
52
|
+
}
|
|
53
|
+
}
|