@xioflow/kernel 0.1.5 → 0.3.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/README.md +50 -5
- package/dist/domain.d.ts +60 -6
- package/dist/domain.d.ts.map +1 -1
- package/dist/domain.js +405 -24
- package/dist/domain.js.map +1 -1
- package/dist/driver/node-driver.d.ts +10 -24
- package/dist/driver/node-driver.d.ts.map +1 -1
- package/dist/driver/node-driver.js +201 -280
- package/dist/driver/node-driver.js.map +1 -1
- package/dist/driver/process-facts.d.ts +25 -0
- package/dist/driver/process-facts.d.ts.map +1 -0
- package/dist/driver/process-facts.js +164 -0
- package/dist/driver/process-facts.js.map +1 -0
- package/dist/driver/sampler.d.ts +51 -0
- package/dist/driver/sampler.d.ts.map +1 -0
- package/dist/driver/sampler.js +224 -0
- package/dist/driver/sampler.js.map +1 -0
- package/dist/driver/types.d.ts +21 -1
- package/dist/driver/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/quick-run.d.ts +27 -0
- package/dist/quick-run.d.ts.map +1 -0
- package/dist/quick-run.js +82 -0
- package/dist/quick-run.js.map +1 -0
- package/dist/recovery/engine.d.ts.map +1 -1
- package/dist/recovery/engine.js +103 -19
- package/dist/recovery/engine.js.map +1 -1
- package/dist/store/sqlite.d.ts +15 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +210 -12
- package/dist/store/sqlite.js.map +1 -1
- package/dist/supervisor/supervisor.d.ts +16 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -1
- package/dist/supervisor/supervisor.js +820 -308
- package/dist/supervisor/supervisor.js.map +1 -1
- package/dist/testing/contract-suite.d.ts.map +1 -1
- package/dist/testing/contract-suite.js +479 -11
- package/dist/testing/contract-suite.js.map +1 -1
- package/dist/types.d.ts +54 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +59 -0
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/@xioflow/kernel)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
6
|
[](./LICENSE)
|
|
7
|
-
[](./ARCHITECTURE.md)
|
|
7
|
+
[](./ARCHITECTURE.en.md)
|
|
8
|
+
[](./ARCHITECTURE.md)
|
|
8
9
|
|
|
9
|
-
A supervised execution kernel for AI agent runtimes. Zero runtime dependencies.
|
|
10
|
+
A supervised execution kernel for AI agent runtimes. Zero runtime dependencies. (See [Architecture & Protocol Specification (EN)](./ARCHITECTURE.en.md) / [中文规范](./ARCHITECTURE.md)).
|
|
10
11
|
|
|
11
12
|
Agent runtimes usually call `spawn()` (or `exec()`) and hope for the best. When the host crashes mid-tool-call, or a cancel cannot be confirmed, they are left with orphan processes, double-applied side effects, and no honest record of what actually happened. This kernel makes those states first-class instead of silent.
|
|
12
13
|
|
|
@@ -38,11 +39,41 @@ npm install @xioflow/kernel
|
|
|
38
39
|
|
|
39
40
|
## Quickstart
|
|
40
41
|
|
|
42
|
+
### 1. Simplest Usage (`quickRun`)
|
|
43
|
+
|
|
44
|
+
For standard tool calls and durable activity execution, `quickRun` automatically manages domain acquisition, Task and Run records, and lock lifecycles:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { quickRun } from '@xioflow/kernel';
|
|
48
|
+
|
|
49
|
+
// 1. One-line supervised execution
|
|
50
|
+
const result = await quickRun({
|
|
51
|
+
execPath: 'npm',
|
|
52
|
+
args: ['test'],
|
|
53
|
+
cwd: '/path/to/workspace',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(result.status, result.exitCode, result.stdout);
|
|
57
|
+
|
|
58
|
+
// 2. Idempotent execution (safe retry for Temporal / LangGraph / durable workflows)
|
|
59
|
+
const idemResult = await quickRun(
|
|
60
|
+
{ execPath: 'git', args: ['commit', '-m', 'chore: update'], cwd: '/path/to/workspace' },
|
|
61
|
+
{ opId: 'workflow-step-42' } // Passing opId guarantees at-most-once execution across retries
|
|
62
|
+
);
|
|
63
|
+
if (idemResult.replayed) {
|
|
64
|
+
console.log('Result retrieved from journal without re-executing process');
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Full Architecture & Fine-Grained Supervision
|
|
69
|
+
|
|
70
|
+
When you need granular control over Tasks, Runs, resource locks, streaming output, or multi-phase workflows:
|
|
71
|
+
|
|
41
72
|
```js
|
|
42
73
|
import { ExecutionDomain, NodePlatformDriver, ProcessSupervisor } from '@xioflow/kernel';
|
|
43
74
|
|
|
44
75
|
// The domain directory will hold domain.db, domain.lock and artifacts/.
|
|
45
|
-
const domain = ExecutionDomain.acquire('/path/to/workspace/.xioflow', 'my-domain');
|
|
76
|
+
const domain = ExecutionDomain.acquire('/path/to/workspace/.xioflow/kernel', 'my-domain');
|
|
46
77
|
const supervisor = new ProcessSupervisor(domain, new NodePlatformDriver());
|
|
47
78
|
|
|
48
79
|
// Every operation belongs to a Run, and every Run to a Task. Register them first:
|
|
@@ -89,6 +120,9 @@ domain.close(); // releases the owner lease and the domain lock
|
|
|
89
120
|
|
|
90
121
|
To cancel, call `await supervisor.cancelOperation('op-1', graceMs)`. It returns `{ stopped, scope, residualPids? }`. The pending `executeProcess` promise then resolves with `status: 'cancelled'`, or with `status: 'indeterminate'` when the driver cannot confirm that the process actually stopped, in which case its leases stay locked.
|
|
91
122
|
|
|
123
|
+
> **Note on In-Flight Join and Cancellation**:
|
|
124
|
+
> When an in-flight operation with the same `opId` and fingerprint is joined concurrently, passing an `abortSignal` to the secondary caller only cancels the secondary caller's own wait promise—it never aborts the underlying process or the primary caller's execution. To deliberately terminate the underlying process, explicitly call `supervisor.cancelOperation(opId)`.
|
|
125
|
+
|
|
92
126
|
## Crash recovery
|
|
93
127
|
|
|
94
128
|
After a restart, reacquire the domain and run the recovery engine:
|
|
@@ -96,7 +130,7 @@ After a restart, reacquire the domain and run the recovery engine:
|
|
|
96
130
|
```js
|
|
97
131
|
import { ExecutionDomain, NodePlatformDriver, RecoveryEngine } from '@xioflow/kernel';
|
|
98
132
|
|
|
99
|
-
const domain = ExecutionDomain.acquire('/path/to/workspace/.xioflow', 'my-domain');
|
|
133
|
+
const domain = ExecutionDomain.acquire('/path/to/workspace/.xioflow/kernel', 'my-domain');
|
|
100
134
|
const report = await new RecoveryEngine(domain, new NodePlatformDriver()).recover();
|
|
101
135
|
```
|
|
102
136
|
|
|
@@ -150,10 +184,21 @@ Releases are tag-driven. A tag always produces a GitHub Release; the npm upload
|
|
|
150
184
|
|
|
151
185
|
1. Bump `version` in `package.json`, commit, and push to `main`.
|
|
152
186
|
2. Push the matching tag, e.g. `git tag v0.1.5 && git push origin v0.1.5`.
|
|
153
|
-
3. `.github/workflows/release.yml` re-runs typecheck, tests and the pack smoke test, refuses a tag that does not match `package.json`, creates the GitHub Release, and — only when the repository variable `NPM_TRUSTED_PUBLISHING_ENABLED` is `true` — publishes with `npm publish --provenance` and verifies the attestation.
|
|
187
|
+
3. `.github/workflows/release.yml` re-runs typecheck, tests and the pack smoke test, refuses a tag that does not match `package.json`, creates the GitHub Release with the packed tarball, `SHA256SUMS` and a build provenance attestation attached, and — only when the repository variable `NPM_TRUSTED_PUBLISHING_ENABLED` is `true` — publishes with `npm publish --provenance` and verifies the attestation.
|
|
154
188
|
|
|
155
189
|
One-time setup, in this order: (1) npmjs.com → package settings → Trusted Publisher → GitHub Actions: organization `Xio-Shark`, repository `xioflow`, workflow filename `release.yml`, environment left empty; (2) set the repository variable `NPM_TRUSTED_PUBLISHING_ENABLED=true`. Until both exist the publish job is skipped and the reason is printed in the `verify` job's log. npm's registry index can lag several minutes behind an upload, so the verification step polls and may need a job re-run.
|
|
156
190
|
|
|
191
|
+
To attach assets to an existing tag without publishing to npm, run the workflow manually: `gh workflow run release.yml -f tag=v0.1.5`. An asset that is already attached is kept if identical and fails the run if it differs; published assets are never overwritten.
|
|
192
|
+
|
|
193
|
+
### Verifying a release
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
gh release download v0.1.5 --repo Xio-Shark/xioflow
|
|
197
|
+
shasum -a 256 -c SHA256SUMS
|
|
198
|
+
gh attestation verify xioflow-kernel-0.1.5.tgz --repo Xio-Shark/xioflow
|
|
199
|
+
npm audit signatures # inside a project that installed @xioflow/kernel from npm
|
|
200
|
+
```
|
|
201
|
+
|
|
157
202
|
## License
|
|
158
203
|
|
|
159
204
|
MIT
|
package/dist/domain.d.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import { SqliteStore } from './store/sqlite.js';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import { PlatformDriver } from './driver/types.js';
|
|
3
|
+
import { Operation, ResourceLease, OwnerRecord, ResourceBudget, DomainBudget, DomainLockedError, ResourceConflictError, EpochFencedError, TerminationReason, AdjudicationRecord, Task, Run } from './types.js';
|
|
4
|
+
export { DomainLockedError, ResourceConflictError, EpochFencedError, AdjudicationRecord };
|
|
5
|
+
export interface DomainStatus {
|
|
6
|
+
domainId: string;
|
|
7
|
+
domainPath: string;
|
|
8
|
+
epoch: number;
|
|
9
|
+
owner: OwnerRecord;
|
|
10
|
+
tasks: Task[];
|
|
11
|
+
runs: Run[];
|
|
12
|
+
activeRuns: Run[];
|
|
13
|
+
operations: (Operation & {
|
|
14
|
+
domainId: string;
|
|
15
|
+
})[];
|
|
16
|
+
unfinishedOperations: (Operation & {
|
|
17
|
+
domainId: string;
|
|
18
|
+
})[];
|
|
19
|
+
leases: (ResourceLease & {
|
|
20
|
+
budget?: ResourceBudget;
|
|
21
|
+
})[];
|
|
22
|
+
}
|
|
4
23
|
export declare class ExecutionDomain {
|
|
5
24
|
readonly domainPath: string;
|
|
6
25
|
readonly domainId: string;
|
|
@@ -9,8 +28,11 @@ export declare class ExecutionDomain {
|
|
|
9
28
|
private lockFd;
|
|
10
29
|
private ownerRecord;
|
|
11
30
|
private domainBudget?;
|
|
31
|
+
private driver?;
|
|
12
32
|
private inMemoryLockedResources;
|
|
33
|
+
private inMemoryAllocatedOps;
|
|
13
34
|
private inMemoryBudgets;
|
|
35
|
+
private waitQueue;
|
|
14
36
|
private heartbeatInterval;
|
|
15
37
|
private isFenced;
|
|
16
38
|
private constructor();
|
|
@@ -20,6 +42,8 @@ export declare class ExecutionDomain {
|
|
|
20
42
|
getEpoch(): number;
|
|
21
43
|
setDomainBudget(budget: DomainBudget): void;
|
|
22
44
|
getDomainBudget(): DomainBudget | undefined;
|
|
45
|
+
get status(): DomainStatus;
|
|
46
|
+
getStatus(): DomainStatus;
|
|
23
47
|
/**
|
|
24
48
|
* 启动时优先重建隔离屏障 (Recovery-Before-Execution)
|
|
25
49
|
*/
|
|
@@ -34,22 +58,52 @@ export declare class ExecutionDomain {
|
|
|
34
58
|
* 刷新租约心跳,若失败则触发代际栅栏与写凭证销毁
|
|
35
59
|
*/
|
|
36
60
|
renewHeartbeat(): void;
|
|
61
|
+
private getActiveOperationIds;
|
|
62
|
+
private diagnoseConflict;
|
|
37
63
|
/**
|
|
38
64
|
* 申请占用资源(支持隔离检查与配额校验)
|
|
39
65
|
*/
|
|
40
66
|
allocateResources(operationId: string, resources: string[], waitedDurationMs?: number, budget?: ResourceBudget): void;
|
|
41
67
|
/**
|
|
42
|
-
*
|
|
68
|
+
* 具备 FIFO 排队等待与诊断的异步资源分配 (P0-5, P0-13)
|
|
69
|
+
*/
|
|
70
|
+
allocateResourcesWithWait(operationId: string, resources: string[], maxWaitMs?: number, budget?: ResourceBudget, isCancelled?: () => boolean): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* 取消在等待队列中的操作 (立即唤醒以完成取消协议)
|
|
73
|
+
*/
|
|
74
|
+
cancelWait(operationId: string): boolean;
|
|
75
|
+
private processWaitQueue;
|
|
76
|
+
setDriver(driver: PlatformDriver): void;
|
|
77
|
+
/**
|
|
78
|
+
* 内部私有资源释放(走代际栅栏并记录 RESOURCES_RELEASED 事件,§0.2 裁决 1 / N6)
|
|
79
|
+
* @internal 仅供 supervisor、recovery engine 与 adjudicate 等内核内部组件调用
|
|
43
80
|
*/
|
|
44
|
-
|
|
81
|
+
internalReleaseResources(operationId: string, resources?: string[]): void;
|
|
45
82
|
/**
|
|
46
|
-
*
|
|
83
|
+
* 人工裁决协议:对 indeterminate 状态进行有审计记录的唯一收口 (ARCHITECTURE §3.6 / P0-7)
|
|
47
84
|
*/
|
|
48
|
-
|
|
85
|
+
adjudicate(opId: string, verdict: 'confirmed_stopped' | 'abandon_with_residuals', actor: string, note?: string): Promise<AdjudicationRecord>;
|
|
86
|
+
/**
|
|
87
|
+
* Run 完成与取消协议 (ARCHITECTURE §3.3)
|
|
88
|
+
*/
|
|
89
|
+
reportRunCancelled(runId: string, reason?: TerminationReason): void;
|
|
90
|
+
reportRunSucceeded(runId: string): void;
|
|
91
|
+
reportRunFailed(runId: string, reason?: TerminationReason): void;
|
|
49
92
|
/**
|
|
50
93
|
* 意图登记协议:先持久化意图与占用,再允许驱动调用
|
|
51
94
|
*/
|
|
52
95
|
registerOperationIntent(op: Operation): void;
|
|
96
|
+
/**
|
|
97
|
+
* 产物回收防线 (ARCHITECTURE §4.3 / N7)
|
|
98
|
+
* 只回收已终态且未被引用的产物,严格拒绝回收未终结或 indeterminate 状态的产物。
|
|
99
|
+
*/
|
|
100
|
+
pruneArtifacts(filter?: {
|
|
101
|
+
olderThanMs?: number;
|
|
102
|
+
prefix?: string;
|
|
103
|
+
}): {
|
|
104
|
+
deleted: string[];
|
|
105
|
+
retained: string[];
|
|
106
|
+
};
|
|
53
107
|
isClosed(): boolean;
|
|
54
108
|
close(): void;
|
|
55
109
|
}
|
package/dist/domain.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAEL,SAAS,
|
|
1
|
+
{"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"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"}
|