@noetic-tools/core 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -11
- package/dist/adapters/in-memory-fs-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-fs-adapter.js +1 -1
- package/dist/adapters/in-memory-shell-adapter.d.ts +1 -1
- package/dist/adapters/in-memory-subprocess/manifest-persistence.d.ts +8 -0
- package/dist/adapters/in-memory-subprocess/manifest-persistence.js +56 -0
- package/dist/adapters/in-memory-subprocess/metadata.d.ts +12 -0
- package/dist/adapters/in-memory-subprocess/metadata.js +58 -0
- package/dist/adapters/in-memory-subprocess/step-completion.d.ts +3 -0
- package/dist/adapters/in-memory-subprocess/step-completion.js +86 -0
- package/dist/adapters/in-memory-subprocess/types.d.ts +88 -0
- package/dist/adapters/in-memory-subprocess/types.js +2 -0
- package/dist/adapters/in-memory-subprocess-adapter.d.ts +12 -4
- package/dist/adapters/in-memory-subprocess-adapter.js +164 -55
- package/dist/adapters/openrouter.d.ts +16 -7
- package/dist/adapters/openrouter.js +68 -7
- package/dist/builders/channel-builder.d.ts +1 -1
- package/dist/builders/control-flow-builders.d.ts +3 -3
- package/dist/builders/control-flow-builders.js +9 -4
- package/dist/builders/every.d.ts +3 -3
- package/dist/builders/every.js +5 -2
- package/dist/builders/layer-provides-builders.d.ts +1 -32
- package/dist/builders/layer-provides-builders.js +1 -33
- package/dist/builders/loop-builder.d.ts +2 -2
- package/dist/builders/loop-builder.js +5 -2
- package/dist/builders/memory-builder.d.ts +1 -1
- package/dist/builders/memory-builder.js +1 -1
- package/dist/builders/provide-builder.d.ts +2 -2
- package/dist/builders/provide-builder.js +5 -2
- package/dist/builders/spawn-builder.d.ts +22 -9
- package/dist/builders/spawn-builder.js +17 -3
- package/dist/builders/step-builders.d.ts +51 -30
- package/dist/builders/step-builders.js +31 -12
- package/dist/builders/tool-builder.d.ts +7 -3
- package/dist/builders/tool-builder.js +3 -1
- package/dist/builders/workflow-hydrator.d.ts +38 -0
- package/dist/builders/workflow-hydrator.js +336 -0
- package/dist/conditions/conditions.d.ts +4 -4
- package/dist/conditions/conditions.js +2 -2
- package/dist/errors/noetic-error.d.ts +1 -1
- package/dist/errors/noetic-error.js +2 -0
- package/dist/{runtime → harness}/agent-harness.d.ts +65 -45
- package/dist/harness/agent-harness.js +576 -0
- package/dist/harness/deps/adapters.d.ts +4 -0
- package/dist/harness/deps/adapters.js +4 -0
- package/dist/harness/deps/interpreter.d.ts +5 -0
- package/dist/harness/deps/interpreter.js +4 -0
- package/dist/harness/deps/memory.d.ts +5 -0
- package/dist/harness/deps/memory.js +4 -0
- package/dist/harness/deps/runtime.d.ts +10 -0
- package/dist/harness/deps/runtime.js +7 -0
- package/dist/harness/deps/types.d.ts +17 -0
- package/dist/harness/deps/types.js +1 -0
- package/dist/harness/model-call.d.ts +50 -0
- package/dist/harness/model-call.js +694 -0
- package/dist/harness/model-schema.d.ts +19 -0
- package/dist/harness/model-schema.js +46 -0
- package/dist/harness/step-bootstrap.d.ts +25 -0
- package/dist/harness/step-bootstrap.js +170 -0
- package/dist/index.d.ts +114 -76
- package/dist/index.js +74 -50
- package/dist/internal-test.d.ts +4 -4
- package/dist/internal-test.js +4 -4
- package/dist/interpreter/action-deps.d.ts +12 -0
- package/dist/interpreter/action-deps.js +10 -0
- package/dist/interpreter/action-types.d.ts +8 -0
- package/dist/interpreter/action-types.js +1 -0
- package/dist/interpreter/collect-tools.d.ts +2 -2
- package/dist/interpreter/collect-tools.js +6 -2
- package/dist/interpreter/execute-action.d.ts +22 -0
- package/dist/interpreter/execute-action.js +527 -0
- package/dist/interpreter/{execute-every.d.ts → execute-control.d.ts} +8 -2
- package/dist/interpreter/execute-control.js +530 -0
- package/dist/interpreter/execute.d.ts +16 -3
- package/dist/interpreter/execute.js +154 -51
- package/dist/interpreter/message-helpers.d.ts +1 -9
- package/dist/interpreter/message-helpers.js +1 -42
- package/dist/interpreter/step-dispatcher.d.ts +61 -0
- package/dist/interpreter/step-dispatcher.js +67 -0
- package/dist/interpreter/typeguards.d.ts +8 -15
- package/dist/interpreter/typeguards.js +4 -21
- package/dist/memory/budget.d.ts +2 -2
- package/dist/memory/budget.js +1 -1
- package/dist/{runtime → memory}/exec-context-factory.d.ts +2 -2
- package/dist/{runtime → memory}/exec-context-factory.js +1 -1
- package/dist/memory/flow-schema.d.ts +52 -0
- package/dist/memory/flow-schema.js +102 -0
- package/dist/memory/function-call-utils.d.ts +1 -1
- package/dist/memory/function-call-utils.js +4 -2
- package/dist/memory/layer-api.d.ts +4 -4
- package/dist/memory/layer-api.js +2 -2
- package/dist/memory/layer-lifecycle.d.ts +6 -6
- package/dist/memory/layer-lifecycle.js +6 -6
- package/dist/memory/layer-provides.d.ts +32 -0
- package/dist/memory/layer-provides.js +33 -0
- package/dist/memory/layer-usage.d.ts +4 -3
- package/dist/memory/layer-usage.js +9 -3
- package/dist/memory/layers/durable-task-state.d.ts +8 -8
- package/dist/memory/layers/durable-task-state.js +2 -2
- package/dist/memory/layers/file-reference.d.ts +1 -1
- package/dist/memory/layers/file-reference.js +303 -247
- package/dist/memory/layers/history-window.d.ts +1 -1
- package/dist/memory/layers/history-window.js +4 -4
- package/dist/memory/layers/observational-memory.d.ts +5 -5
- package/dist/memory/layers/observational-memory.js +2 -3
- package/dist/memory/layers/plan.d.ts +2 -2
- package/dist/memory/layers/plan.js +15 -10
- package/dist/memory/layers/static-content.d.ts +2 -2
- package/dist/memory/layers/static-content.js +1 -1
- package/dist/memory/layers/steering.d.ts +7 -7
- package/dist/memory/layers/steering.js +5 -5
- package/dist/memory/layers/tool-memory-layer.d.ts +2 -2
- package/dist/memory/layers/tool-memory-layer.js +1 -1
- package/dist/memory/layers/working-memory.d.ts +7 -7
- package/dist/memory/layers/working-memory.js +4 -4
- package/dist/memory/projector.d.ts +2 -2
- package/dist/memory/scope.d.ts +1 -1
- package/dist/memory/strip-unresolved.d.ts +1 -1
- package/dist/observability/span-impl.d.ts +1 -1
- package/dist/observability/trace-exporter.d.ts +2 -2
- package/dist/observability/trace-exporter.js +1 -1
- package/dist/patterns/dynamic-workflow.d.ts +55 -0
- package/dist/patterns/dynamic-workflow.js +194 -0
- package/dist/patterns/flow.d.ts +2 -52
- package/dist/patterns/flow.js +1 -103
- package/dist/patterns/interview.d.ts +2 -2
- package/dist/patterns/interview.js +5 -5
- package/dist/patterns/plans.d.ts +6 -6
- package/dist/patterns/plans.js +4 -4
- package/dist/patterns/ralph-wiggum.d.ts +4 -4
- package/dist/patterns/ralph-wiggum.js +5 -5
- package/dist/patterns/react.d.ts +3 -3
- package/dist/patterns/react.js +5 -5
- package/dist/portable/adapters.d.ts +6 -0
- package/dist/portable/adapters.js +6 -0
- package/dist/portable/ask-user.d.ts +4 -0
- package/dist/portable/ask-user.js +2 -0
- package/dist/portable/builders.d.ts +12 -0
- package/dist/portable/builders.js +12 -0
- package/dist/portable/memory-layers.d.ts +12 -0
- package/dist/portable/memory-layers.js +12 -0
- package/dist/portable/runtime.d.ts +4 -0
- package/dist/portable/runtime.js +4 -0
- package/dist/portable/until.d.ts +4 -0
- package/dist/portable/until.js +4 -0
- package/dist/portable.d.ts +8 -26
- package/dist/portable.js +8 -34
- package/dist/runtime/broadcaster-utils.d.ts +7 -3
- package/dist/runtime/broadcaster-utils.js +11 -6
- package/dist/runtime/channel-store.d.ts +1 -1
- package/dist/runtime/channel-store.js +2 -2
- package/dist/runtime/context-deps.d.ts +4 -0
- package/dist/runtime/context-deps.js +2 -0
- package/dist/runtime/context-impl.d.ts +42 -12
- package/dist/runtime/context-impl.js +52 -5
- package/dist/runtime/context-type-deps.d.ts +14 -0
- package/dist/runtime/context-type-deps.js +1 -0
- package/dist/runtime/cwd-helpers.d.ts +1 -1
- package/dist/runtime/detached-handle.d.ts +52 -4
- package/dist/runtime/detached-handle.js +218 -14
- package/dist/runtime/durable/checkpoint-store.d.ts +38 -0
- package/dist/runtime/durable/checkpoint-store.js +101 -0
- package/dist/runtime/durable/detached-signal.d.ts +37 -0
- package/dist/runtime/durable/detached-signal.js +52 -0
- package/dist/runtime/durable/harness-checkpoints.d.ts +51 -0
- package/dist/runtime/durable/harness-checkpoints.js +104 -0
- package/dist/runtime/durable/index.d.ts +10 -0
- package/dist/runtime/durable/index.js +5 -0
- package/dist/runtime/durable/runnable-loop.d.ts +100 -0
- package/dist/runtime/durable/runnable-loop.js +90 -0
- package/dist/runtime/durable/session-seed.d.ts +28 -0
- package/dist/runtime/durable/session-seed.js +27 -0
- package/dist/runtime/durable/stall-nudge.d.ts +82 -0
- package/dist/runtime/durable/stall-nudge.js +87 -0
- package/dist/runtime/event-broadcaster.d.ts +1 -1
- package/dist/runtime/in-memory-storage.d.ts +1 -1
- package/dist/runtime/in-memory-storage.js +1 -1
- package/dist/runtime/item-log-impl.d.ts +3 -3
- package/dist/runtime/item-log-impl.js +1 -1
- package/dist/runtime/message-queue.d.ts +2 -2
- package/dist/runtime/session-runner.d.ts +7 -7
- package/dist/runtime/session-runner.js +3 -3
- package/dist/runtime/session-streams.d.ts +3 -3
- package/dist/runtime/session-streams.js +1 -1
- package/dist/runtime/step-registry.d.ts +42 -0
- package/dist/runtime/step-registry.js +77 -0
- package/dist/runtime/tool-memory.d.ts +3 -3
- package/dist/schemas/item.d.ts +1 -1
- package/dist/schemas/workflow.d.ts +139 -0
- package/dist/schemas/workflow.js +232 -0
- package/dist/types/ask-user-types.d.ts +57 -0
- package/dist/types/ask-user-types.js +74 -0
- package/dist/types/checkpoint.d.ts +98 -0
- package/dist/types/checkpoint.js +81 -0
- package/dist/types/common.d.ts +1 -50
- package/dist/types/context-parts/item-log.d.ts +6 -0
- package/dist/types/context-parts/item-log.js +1 -0
- package/dist/types/context-parts/layer-usage.d.ts +18 -0
- package/dist/types/context-parts/layer-usage.js +1 -0
- package/dist/types/context.d.ts +140 -31
- package/dist/types/error.d.ts +15 -0
- package/dist/types/harness-result.d.ts +2 -2
- package/dist/types/items.d.ts +18 -2
- package/dist/types/memory-context.d.ts +39 -0
- package/dist/types/memory-context.js +1 -0
- package/dist/types/memory.d.ts +38 -68
- package/dist/types/mutable-context.d.ts +3 -3
- package/dist/types/runtime.d.ts +14 -13
- package/dist/types/steering.d.ts +2 -2
- package/dist/types/step-registrar.d.ts +46 -0
- package/dist/types/step-registrar.js +56 -0
- package/dist/types/step.d.ts +34 -9
- package/dist/types/subprocess-adapter.d.ts +119 -3
- package/dist/types/subprocess-adapter.js +2 -0
- package/dist/types/tool-context.d.ts +6 -6
- package/dist/types/tool.d.ts +64 -0
- package/dist/types/tool.js +1 -0
- package/dist/unstable.d.ts +9 -9
- package/dist/unstable.js +7 -7
- package/dist/until/combinators.d.ts +1 -1
- package/dist/until/never.d.ts +1 -1
- package/dist/until/predicates.d.ts +13 -4
- package/dist/until/predicates.js +16 -2
- package/dist/util/message-helpers.d.ts +17 -0
- package/dist/util/message-helpers.js +64 -0
- package/package.json +6 -4
- package/dist/adapters/local-fs-adapter.d.ts +0 -3
- package/dist/adapters/local-fs-adapter.js +0 -43
- package/dist/adapters/local-shell-adapter.d.ts +0 -51
- package/dist/adapters/local-shell-adapter.js +0 -167
- package/dist/adapters/local-subprocess-adapter.d.ts +0 -16
- package/dist/adapters/local-subprocess-adapter.js +0 -187
- package/dist/conditions/index.d.ts +0 -3
- package/dist/conditions/index.js +0 -2
- package/dist/interpreter/execute-branch.d.ts +0 -3
- package/dist/interpreter/execute-branch.js +0 -10
- package/dist/interpreter/execute-every.js +0 -143
- package/dist/interpreter/execute-fork.d.ts +0 -3
- package/dist/interpreter/execute-fork.js +0 -215
- package/dist/interpreter/execute-llm.d.ts +0 -4
- package/dist/interpreter/execute-llm.js +0 -217
- package/dist/interpreter/execute-loop.d.ts +0 -3
- package/dist/interpreter/execute-loop.js +0 -171
- package/dist/interpreter/execute-provide.d.ts +0 -10
- package/dist/interpreter/execute-provide.js +0 -64
- package/dist/interpreter/execute-run.d.ts +0 -3
- package/dist/interpreter/execute-run.js +0 -39
- package/dist/interpreter/execute-spawn.d.ts +0 -11
- package/dist/interpreter/execute-spawn.js +0 -113
- package/dist/interpreter/execute-tool.d.ts +0 -5
- package/dist/interpreter/execute-tool.js +0 -81
- package/dist/runtime/agent-harness.js +0 -1170
- /package/dist/{interpreter → util}/framework-cast.d.ts +0 -0
- /package/dist/{interpreter → util}/framework-cast.js +0 -0
package/README.md
CHANGED
|
@@ -5,33 +5,53 @@ Core primitives for the [Noetic](https://github.com/mattapperson/noetic) agent f
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install @noetic-tools/core
|
|
8
|
+
npm install @noetic-tools/core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
`@openrouter/agent`
|
|
11
|
+
`@noetic-tools/core` depends on `@openrouter/agent` (the OpenRouter LLM adapter) and `zod`; your package manager installs both automatically. Runs on Node.js 18+ and Bun.
|
|
12
|
+
|
|
13
|
+
## Set your API key
|
|
14
|
+
|
|
15
|
+
Noetic talks to LLMs through [OpenRouter](https://openrouter.ai). Grab a key at [openrouter.ai/keys](https://openrouter.ai/keys) and export it — the runtime reads `OPENROUTER_API_KEY` by default:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
export OPENROUTER_API_KEY=sk-or-...
|
|
19
|
+
```
|
|
12
20
|
|
|
13
21
|
## Quick start
|
|
14
22
|
|
|
23
|
+
A complete, runnable program. Save it as `agent.mjs` and run `node agent.mjs`:
|
|
24
|
+
|
|
15
25
|
```ts
|
|
16
26
|
import { AgentHarness, step } from '@noetic-tools/core';
|
|
17
27
|
|
|
18
|
-
|
|
19
|
-
model: 'anthropic/claude-sonnet-4',
|
|
20
|
-
llm: { apiKey: process.env.OPENROUTER_API_KEY },
|
|
21
|
-
});
|
|
22
|
-
|
|
28
|
+
// 1. Define a single LLM step. `model` and `instructions` live here.
|
|
23
29
|
const greet = step.llm({
|
|
24
30
|
id: 'greet',
|
|
25
|
-
|
|
31
|
+
model: 'openai/gpt-4o-mini', // any OpenRouter model id (provider/model)
|
|
32
|
+
instructions: 'You are a friendly assistant. Greet the user by name in one short sentence.',
|
|
26
33
|
});
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
// 2. Create the harness. `name` and `params` are required; `llm` selects the provider.
|
|
36
|
+
const harness = new AgentHarness({
|
|
37
|
+
name: 'greeter',
|
|
38
|
+
params: {},
|
|
39
|
+
llm: { provider: 'openrouter' }, // apiKey defaults to process.env.OPENROUTER_API_KEY
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// 3. Run the step with a context and print the model's reply.
|
|
43
|
+
const ctx = harness.createContext();
|
|
44
|
+
const reply = await harness.run(greet, 'Sam', ctx);
|
|
45
|
+
console.log(reply); // -> "Hello, Sam! How can I help you today?"
|
|
30
46
|
```
|
|
31
47
|
|
|
48
|
+
**Using TypeScript?** Save the file as `agent.ts` and run it with `bunx tsx agent.ts` (or `bun run agent.ts`) — both execute TypeScript directly, no build step.
|
|
49
|
+
|
|
50
|
+
Model ids use OpenRouter's `provider/model` format (e.g. `openai/gpt-4o`, `anthropic/claude-sonnet-4`) — see [openrouter.ai/models](https://openrouter.ai/models).
|
|
51
|
+
|
|
32
52
|
## Documentation
|
|
33
53
|
|
|
34
|
-
Full docs, specs, and examples live in the [main repo](https://github.com/mattapperson/noetic).
|
|
54
|
+
Full docs, specs, and examples live at [noetic.tools/docs](https://noetic.tools/docs) and in the [main repo](https://github.com/mattapperson/noetic).
|
|
35
55
|
|
|
36
56
|
## License
|
|
37
57
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { FsAdapter } from '../types/fs-adapter';
|
|
1
|
+
import type { FsAdapter } from '../types/fs-adapter.js';
|
|
2
2
|
/** @public Create a process-local, POSIX-like in-memory filesystem adapter. */
|
|
3
3
|
export declare function createInMemoryFsAdapter(seed?: Record<string, string | Uint8Array>): FsAdapter;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../../types/memory.js';
|
|
2
|
+
import type { StepSubprocessRequest, SubprocessHandle } from '../../types/subprocess-adapter.js';
|
|
3
|
+
import type { InMemoryStepManifest } from './types.js';
|
|
4
|
+
export declare function isStepManifest(value: unknown): value is InMemoryStepManifest;
|
|
5
|
+
export declare function persistStepManifest(storage: StorageAdapter, request: StepSubprocessRequest, handle: SubprocessHandle): Promise<void>;
|
|
6
|
+
export declare function clearManifest(storage: StorageAdapter, handleId: string): Promise<void>;
|
|
7
|
+
export declare function loadManifest(storage: StorageAdapter, handleId: string): Promise<InMemoryStepManifest | null>;
|
|
8
|
+
export declare function listManifests(storage: StorageAdapter): Promise<InMemoryStepManifest[]>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { isRecord } from './metadata.js';
|
|
2
|
+
//#region Key layout
|
|
3
|
+
const IN_MEMORY_MANIFEST_PREFIX = 'inMemorySubprocess:manifest:';
|
|
4
|
+
function manifestKey(handleId) {
|
|
5
|
+
return `${IN_MEMORY_MANIFEST_PREFIX}${handleId}`;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region Guards
|
|
9
|
+
export function isStepManifest(value) {
|
|
10
|
+
if (!isRecord(value)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return (value.kind === 'step' &&
|
|
14
|
+
typeof value.handleId === 'string' &&
|
|
15
|
+
typeof value.stepId === 'string' &&
|
|
16
|
+
typeof value.executionId === 'string' &&
|
|
17
|
+
typeof value.startedAt === 'string' &&
|
|
18
|
+
isRecord(value.overrides));
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region CRUD
|
|
22
|
+
export async function persistStepManifest(storage, request, handle) {
|
|
23
|
+
const manifest = {
|
|
24
|
+
kind: 'step',
|
|
25
|
+
handleId: handle.id,
|
|
26
|
+
stepId: request.stepId,
|
|
27
|
+
executionId: request.executionId,
|
|
28
|
+
serializedInput: request.serializedInput,
|
|
29
|
+
overrides: request.overrides,
|
|
30
|
+
startedAt: handle.startedAt,
|
|
31
|
+
reattachMode: 'replay',
|
|
32
|
+
};
|
|
33
|
+
await storage.set(manifestKey(handle.id), manifest);
|
|
34
|
+
}
|
|
35
|
+
export async function clearManifest(storage, handleId) {
|
|
36
|
+
await storage.delete(manifestKey(handleId));
|
|
37
|
+
}
|
|
38
|
+
export async function loadManifest(storage, handleId) {
|
|
39
|
+
const raw = await storage.get(manifestKey(handleId));
|
|
40
|
+
if (raw === null) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return isStepManifest(raw) ? raw : null;
|
|
44
|
+
}
|
|
45
|
+
export async function listManifests(storage) {
|
|
46
|
+
const keys = await storage.list(IN_MEMORY_MANIFEST_PREFIX);
|
|
47
|
+
const out = [];
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const raw = await storage.get(key);
|
|
50
|
+
if (isStepManifest(raw)) {
|
|
51
|
+
out.push(raw);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProcessSubprocessRequest, SerializedError, StepSubprocessRequest, SubprocessHandleMetadata, SubprocessRequest } from '../../types/subprocess-adapter.js';
|
|
2
|
+
export declare function nowIso(): string;
|
|
3
|
+
export declare function isStepRequest(request: SubprocessRequest): request is StepSubprocessRequest;
|
|
4
|
+
export declare function isProcessRequest(request: SubprocessRequest): request is ProcessSubprocessRequest;
|
|
5
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
6
|
+
/** Convert a thrown value into a `SerializedError`. `NoeticErrorImpl`
|
|
7
|
+
* instances (and anything exposing `noeticError`) retain their structured
|
|
8
|
+
* payload so consumers can rehydrate typed kinds without a brittle message
|
|
9
|
+
* regex. */
|
|
10
|
+
export declare function serializeError(err: unknown): SerializedError;
|
|
11
|
+
export declare function buildProcessMetadata(request: ProcessSubprocessRequest): SubprocessHandleMetadata;
|
|
12
|
+
export declare function buildStepMetadata(request: StepSubprocessRequest): SubprocessHandleMetadata;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region Time
|
|
2
|
+
export function nowIso() {
|
|
3
|
+
return new Date().toISOString();
|
|
4
|
+
}
|
|
5
|
+
//#endregion
|
|
6
|
+
//#region Type guards
|
|
7
|
+
export function isStepRequest(request) {
|
|
8
|
+
return request.kind === 'step';
|
|
9
|
+
}
|
|
10
|
+
export function isProcessRequest(request) {
|
|
11
|
+
return request.kind !== 'step';
|
|
12
|
+
}
|
|
13
|
+
export function isRecord(value) {
|
|
14
|
+
return typeof value === 'object' && value !== null;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region Error serialisation
|
|
18
|
+
/** Convert a thrown value into a `SerializedError`. `NoeticErrorImpl`
|
|
19
|
+
* instances (and anything exposing `noeticError`) retain their structured
|
|
20
|
+
* payload so consumers can rehydrate typed kinds without a brittle message
|
|
21
|
+
* regex. */
|
|
22
|
+
export function serializeError(err) {
|
|
23
|
+
if (err instanceof Error) {
|
|
24
|
+
const payload = {
|
|
25
|
+
message: err.message,
|
|
26
|
+
name: err.name,
|
|
27
|
+
stack: err.stack,
|
|
28
|
+
};
|
|
29
|
+
if (isRecord(err) && 'noeticError' in err) {
|
|
30
|
+
payload.noeticError = err.noeticError;
|
|
31
|
+
}
|
|
32
|
+
return payload;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
message: typeof err === 'string' ? err : String(err),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region Metadata builders
|
|
40
|
+
export function buildProcessMetadata(request) {
|
|
41
|
+
return {
|
|
42
|
+
...(request.metadata ?? {}),
|
|
43
|
+
runtime: 'in-memory',
|
|
44
|
+
command: request.command,
|
|
45
|
+
args: request.args ?? [],
|
|
46
|
+
cwd: request.cwd,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function buildStepMetadata(request) {
|
|
50
|
+
return {
|
|
51
|
+
...(request.metadata ?? {}),
|
|
52
|
+
runtime: 'in-memory',
|
|
53
|
+
kind: 'step',
|
|
54
|
+
stepId: request.stepId,
|
|
55
|
+
executionId: request.executionId,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CompleteProcessRunParams, CompleteStepRunParams } from './types.js';
|
|
2
|
+
export declare function completeProcessRun({ request, handle, run, handles, active, save, clearIfDurable, }: CompleteProcessRunParams): Promise<void>;
|
|
3
|
+
export declare function completeStepRun({ request, handle, stepRunner, handles, active, save, clearIfDurable, }: CompleteStepRunParams): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { nowIso, serializeError } from './metadata.js';
|
|
2
|
+
//#region Completion handlers
|
|
3
|
+
export async function completeProcessRun({ request, handle, run, handles, active, save, clearIfDurable, }) {
|
|
4
|
+
try {
|
|
5
|
+
await run?.(request, handle);
|
|
6
|
+
const latest = handles.get(handle.id);
|
|
7
|
+
if (!latest || latest.status !== 'running') {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
active.delete(handle.id);
|
|
11
|
+
await save({
|
|
12
|
+
...latest,
|
|
13
|
+
status: 'completed',
|
|
14
|
+
updatedAt: nowIso(),
|
|
15
|
+
});
|
|
16
|
+
// Clear the durable handle manifest once the run has terminated so
|
|
17
|
+
// `listLive()` stops returning this handle. Without this call the
|
|
18
|
+
// manifest would persist until `stop()` was called, making every
|
|
19
|
+
// completed step look live from the adapter's persistence layer.
|
|
20
|
+
await clearIfDurable(handle.id);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
active.delete(handle.id);
|
|
24
|
+
const latest = handles.get(handle.id) ?? handle;
|
|
25
|
+
await save({
|
|
26
|
+
...latest,
|
|
27
|
+
status: 'failed',
|
|
28
|
+
updatedAt: nowIso(),
|
|
29
|
+
metadata: {
|
|
30
|
+
...(latest.metadata ?? {}),
|
|
31
|
+
error: serializeError(err),
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
// Same rationale as the success branch above — clear the durable
|
|
35
|
+
// manifest so `listLive()` doesn't resurrect this handle on reboot.
|
|
36
|
+
await clearIfDurable(handle.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function completeStepRun({ request, handle, stepRunner, handles, active, save, clearIfDurable, }) {
|
|
40
|
+
try {
|
|
41
|
+
const executor = request._localExecutor;
|
|
42
|
+
let result;
|
|
43
|
+
if (executor) {
|
|
44
|
+
result = await executor();
|
|
45
|
+
}
|
|
46
|
+
else if (stepRunner) {
|
|
47
|
+
result = await stepRunner(request, handle);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
throw new Error(`In-memory subprocess adapter has no executor for step "${request.stepId}". ` +
|
|
51
|
+
'Provide a stepRunner option or dispatch via AgentHarness.');
|
|
52
|
+
}
|
|
53
|
+
const latest = handles.get(handle.id);
|
|
54
|
+
if (!latest || latest.status !== 'running') {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
active.delete(handle.id);
|
|
58
|
+
await save({
|
|
59
|
+
...latest,
|
|
60
|
+
status: 'completed',
|
|
61
|
+
updatedAt: nowIso(),
|
|
62
|
+
metadata: {
|
|
63
|
+
...(latest.metadata ?? {}),
|
|
64
|
+
result,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// Clear the durable step manifest once the run has terminated —
|
|
68
|
+
// see `completeProcessRun` above for the rationale.
|
|
69
|
+
await clearIfDurable(handle.id);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
active.delete(handle.id);
|
|
73
|
+
const latest = handles.get(handle.id) ?? handle;
|
|
74
|
+
await save({
|
|
75
|
+
...latest,
|
|
76
|
+
status: 'failed',
|
|
77
|
+
updatedAt: nowIso(),
|
|
78
|
+
metadata: {
|
|
79
|
+
...(latest.metadata ?? {}),
|
|
80
|
+
error: serializeError(err),
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
await clearIfDurable(handle.id);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { StorageAdapter } from '../../types/memory.js';
|
|
2
|
+
import type { ProcessSubprocessRequest, StepSubprocessRequest, SubprocessHandle, SubprocessHandleMetadata, SubprocessRequest } from '../../types/subprocess-adapter.js';
|
|
3
|
+
/**
|
|
4
|
+
* @public
|
|
5
|
+
* Options for `createInMemorySubprocessAdapter`. All hooks are optional;
|
|
6
|
+
* when none is provided the adapter still handles `spawn` + `stop` +
|
|
7
|
+
* lifecycle transitions for test-double purposes.
|
|
8
|
+
*/
|
|
9
|
+
export interface CreateInMemorySubprocessAdapterOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Invoked for `kind: 'process'` requests (the classic test-double hook).
|
|
12
|
+
* When absent the adapter marks the handle `completed` on the next
|
|
13
|
+
* microtask without running any body.
|
|
14
|
+
*/
|
|
15
|
+
run?: (request: SubprocessRequest, handle: SubprocessHandle) => Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Invoked for `kind: 'step'` requests that do not already carry a
|
|
18
|
+
* `_localExecutor` closure. Callers swap this out to simulate step
|
|
19
|
+
* execution in tests; the default harness-bound adapter provides an
|
|
20
|
+
* implementation that invokes the in-process execute pipeline.
|
|
21
|
+
*/
|
|
22
|
+
stepRunner?: (request: StepSubprocessRequest, handle: SubprocessHandle) => Promise<unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Optional synchronous metadata injector. Called during `spawn()` before
|
|
25
|
+
* the handle is returned, its result is merged on top of the adapter's
|
|
26
|
+
* intrinsic metadata (runtime, command/args/cwd, etc.). Use this to stamp
|
|
27
|
+
* pid / pidStarttime / test-only fields without having to wrap the adapter.
|
|
28
|
+
*
|
|
29
|
+
* Keys returned here win over the adapter's defaults — tests can override
|
|
30
|
+
* `runtime` if they need to simulate a different adapter variant.
|
|
31
|
+
*/
|
|
32
|
+
metadataInjector?: (request: SubprocessRequest) => Partial<SubprocessHandleMetadata>;
|
|
33
|
+
/**
|
|
34
|
+
* Optional `StorageAdapter` that gives the in-memory adapter a durable
|
|
35
|
+
* handle manifest. When supplied, `spawn()` persists a manifest per
|
|
36
|
+
* handle and `reattach(handleId)` / `listLive()` consult it across adapter
|
|
37
|
+
* restarts. When absent the adapter is purely ephemeral — matching the
|
|
38
|
+
* test-double semantics established in Phase A.
|
|
39
|
+
*
|
|
40
|
+
* For in-memory the "reattached" handle is an idempotent re-run of the
|
|
41
|
+
* step from the persisted `serializedInput`. We're honest about that in
|
|
42
|
+
* the manifest (`reattachMode: 'replay'`) — this is the sanest in-memory
|
|
43
|
+
* semantic because there is no long-lived process to bind back to.
|
|
44
|
+
*/
|
|
45
|
+
storage?: StorageAdapter;
|
|
46
|
+
}
|
|
47
|
+
export interface CompleteProcessRunParams {
|
|
48
|
+
request: ProcessSubprocessRequest;
|
|
49
|
+
handle: SubprocessHandle;
|
|
50
|
+
run: CreateInMemorySubprocessAdapterOptions['run'];
|
|
51
|
+
handles: Map<string, SubprocessHandle>;
|
|
52
|
+
active: Set<string>;
|
|
53
|
+
save: (handle: SubprocessHandle) => Promise<SubprocessHandle>;
|
|
54
|
+
/** Invoked on terminal transitions so the durable handle manifest
|
|
55
|
+
* does not linger after the step is done — otherwise `listLive()`
|
|
56
|
+
* would return phantom handles for every step ever completed
|
|
57
|
+
* against this storage. No-op when no storage was configured. */
|
|
58
|
+
clearIfDurable: (handleId: string) => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
export interface CompleteStepRunParams {
|
|
61
|
+
request: StepSubprocessRequest;
|
|
62
|
+
handle: SubprocessHandle;
|
|
63
|
+
stepRunner: CreateInMemorySubprocessAdapterOptions['stepRunner'];
|
|
64
|
+
handles: Map<string, SubprocessHandle>;
|
|
65
|
+
active: Set<string>;
|
|
66
|
+
save: (handle: SubprocessHandle) => Promise<SubprocessHandle>;
|
|
67
|
+
/** See `CompleteProcessRunParams.clearIfDurable`. */
|
|
68
|
+
clearIfDurable: (handleId: string) => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Persistable manifest for an in-memory handle. Written to the
|
|
72
|
+
* StorageAdapter on spawn and cleared on stop — a restart can list live
|
|
73
|
+
* handles via this prefix and re-run the step from `serializedInput`.
|
|
74
|
+
*
|
|
75
|
+
* Scoped to `step`-kind requests because `process`-kind handles carry no
|
|
76
|
+
* replayable body (they represent already-launched OS processes whose state
|
|
77
|
+
* is not ours to recreate).
|
|
78
|
+
*/
|
|
79
|
+
export interface InMemoryStepManifest {
|
|
80
|
+
kind: 'step';
|
|
81
|
+
handleId: string;
|
|
82
|
+
stepId: string;
|
|
83
|
+
executionId: string;
|
|
84
|
+
serializedInput: unknown;
|
|
85
|
+
overrides: StepSubprocessRequest['overrides'];
|
|
86
|
+
startedAt: string;
|
|
87
|
+
reattachMode: 'replay';
|
|
88
|
+
}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import type { SubprocessAdapter
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { SubprocessAdapter } from '../types/subprocess-adapter.js';
|
|
2
|
+
import type { CreateInMemorySubprocessAdapterOptions } from './in-memory-subprocess/types.js';
|
|
3
|
+
export type { CreateInMemorySubprocessAdapterOptions } from './in-memory-subprocess/types.js';
|
|
4
|
+
/**
|
|
5
|
+
* @public
|
|
6
|
+
* In-memory `SubprocessAdapter` for tests and non-durable in-process
|
|
7
|
+
* step execution. When an optional `StorageAdapter` is supplied the
|
|
8
|
+
* adapter persists per-handle manifests so `reattach()` / `listLive()`
|
|
9
|
+
* work across adapter instances — the reattached handle is an idempotent
|
|
10
|
+
* re-run of the step from the persisted `serializedInput`, not a bind to
|
|
11
|
+
* a live process (there is none to bind to for in-memory).
|
|
12
|
+
*/
|
|
5
13
|
export declare function createInMemorySubprocessAdapter(options?: CreateInMemorySubprocessAdapterOptions): SubprocessAdapter;
|
|
@@ -1,68 +1,135 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { clearManifest, listManifests, loadManifest, persistStepManifest, } from './in-memory-subprocess/manifest-persistence.js';
|
|
2
|
+
import { buildProcessMetadata, buildStepMetadata, isProcessRequest, isStepRequest, nowIso, } from './in-memory-subprocess/metadata.js';
|
|
3
|
+
import { completeProcessRun, completeStepRun } from './in-memory-subprocess/step-completion.js';
|
|
4
|
+
//#region Factory
|
|
5
|
+
/**
|
|
6
|
+
* @public
|
|
7
|
+
* In-memory `SubprocessAdapter` for tests and non-durable in-process
|
|
8
|
+
* step execution. When an optional `StorageAdapter` is supplied the
|
|
9
|
+
* adapter persists per-handle manifests so `reattach()` / `listLive()`
|
|
10
|
+
* work across adapter instances — the reattached handle is an idempotent
|
|
11
|
+
* re-run of the step from the persisted `serializedInput`, not a bind to
|
|
12
|
+
* a live process (there is none to bind to for in-memory).
|
|
13
|
+
*/
|
|
14
|
+
export function createInMemorySubprocessAdapter(options = {}) {
|
|
15
|
+
const handles = new Map();
|
|
16
|
+
const active = new Set();
|
|
17
|
+
const storage = options.storage;
|
|
18
|
+
async function save(handle) {
|
|
19
|
+
handles.set(handle.id, handle);
|
|
20
|
+
return handle;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Persist a step manifest after spawn so `reattach` and `listLive` can
|
|
24
|
+
* find this handle again on a subsequent adapter instance. Errors are
|
|
25
|
+
* logged rather than propagated — a failed manifest write must not break
|
|
26
|
+
* otherwise-successful step dispatch.
|
|
27
|
+
*/
|
|
28
|
+
async function persistIfDurable(request, handle) {
|
|
29
|
+
if (!storage) {
|
|
9
30
|
return;
|
|
10
31
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
32
|
+
try {
|
|
33
|
+
await persistStepManifest(storage, request, handle);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
console.warn(`createInMemorySubprocessAdapter: failed to persist manifest for "${handle.id}":`, err);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function clearIfDurable(handleId) {
|
|
40
|
+
if (!storage) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await clearManifest(storage, handleId);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.warn(`createInMemorySubprocessAdapter: failed to clear manifest for "${handleId}":`, err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function injectMetadata(request, intrinsic) {
|
|
51
|
+
if (!options.metadataInjector) {
|
|
52
|
+
return intrinsic;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...intrinsic,
|
|
56
|
+
...options.metadataInjector(request),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function spawnStepHandle(request) {
|
|
60
|
+
const now = nowIso();
|
|
61
|
+
const handle = {
|
|
62
|
+
id: `subprocess-${crypto.randomUUID()}`,
|
|
63
|
+
status: 'running',
|
|
64
|
+
startedAt: now,
|
|
65
|
+
updatedAt: now,
|
|
66
|
+
metadata: injectMetadata(request, buildStepMetadata(request)),
|
|
67
|
+
};
|
|
68
|
+
active.add(handle.id);
|
|
69
|
+
await save(handle);
|
|
70
|
+
await persistIfDurable(request, handle);
|
|
71
|
+
void completeStepRun({
|
|
72
|
+
request,
|
|
73
|
+
handle,
|
|
74
|
+
stepRunner: options.stepRunner,
|
|
75
|
+
handles,
|
|
76
|
+
active,
|
|
77
|
+
save,
|
|
78
|
+
clearIfDurable,
|
|
16
79
|
});
|
|
80
|
+
return handle;
|
|
17
81
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
82
|
+
async function spawnProcessHandle(request) {
|
|
83
|
+
const now = nowIso();
|
|
84
|
+
const handle = {
|
|
85
|
+
id: `subprocess-${crypto.randomUUID()}`,
|
|
86
|
+
status: 'running',
|
|
87
|
+
startedAt: now,
|
|
88
|
+
updatedAt: now,
|
|
89
|
+
metadata: injectMetadata(request, buildProcessMetadata(request)),
|
|
90
|
+
};
|
|
91
|
+
active.add(handle.id);
|
|
92
|
+
await save(handle);
|
|
93
|
+
void completeProcessRun({
|
|
94
|
+
request,
|
|
95
|
+
handle,
|
|
96
|
+
run: options.run,
|
|
97
|
+
handles,
|
|
98
|
+
active,
|
|
99
|
+
save,
|
|
100
|
+
clearIfDurable,
|
|
101
|
+
});
|
|
102
|
+
return handle;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Build a hydrated handle from a persisted manifest. Status is `running`
|
|
106
|
+
* so consumers see the handle as live; a subsequent `reattach` call
|
|
107
|
+
* re-dispatches the step via the supplied `stepRunner`.
|
|
108
|
+
*/
|
|
109
|
+
function handleFromManifest(manifest) {
|
|
110
|
+
return {
|
|
111
|
+
id: manifest.handleId,
|
|
112
|
+
status: 'running',
|
|
113
|
+
startedAt: manifest.startedAt,
|
|
24
114
|
updatedAt: nowIso(),
|
|
25
115
|
metadata: {
|
|
26
|
-
|
|
27
|
-
|
|
116
|
+
runtime: 'in-memory',
|
|
117
|
+
kind: 'step',
|
|
118
|
+
stepId: manifest.stepId,
|
|
119
|
+
executionId: manifest.executionId,
|
|
120
|
+
reattachMode: manifest.reattachMode,
|
|
28
121
|
},
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
export function createInMemorySubprocessAdapter(options = {}) {
|
|
33
|
-
const handles = new Map();
|
|
34
|
-
const active = new Set();
|
|
35
|
-
async function save(handle) {
|
|
36
|
-
handles.set(handle.id, handle);
|
|
37
|
-
return handle;
|
|
122
|
+
};
|
|
38
123
|
}
|
|
39
124
|
return {
|
|
40
125
|
async spawn(request) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
...(request.metadata ?? {}),
|
|
49
|
-
runtime: 'in-memory',
|
|
50
|
-
command: request.command,
|
|
51
|
-
args: request.args ?? [],
|
|
52
|
-
cwd: request.cwd,
|
|
53
|
-
},
|
|
54
|
-
};
|
|
55
|
-
active.add(handle.id);
|
|
56
|
-
await save(handle);
|
|
57
|
-
void completeInMemoryRun({
|
|
58
|
-
request,
|
|
59
|
-
handle,
|
|
60
|
-
run: options.run,
|
|
61
|
-
handles,
|
|
62
|
-
active,
|
|
63
|
-
save,
|
|
64
|
-
});
|
|
65
|
-
return handle;
|
|
126
|
+
if (isStepRequest(request)) {
|
|
127
|
+
return spawnStepHandle(request);
|
|
128
|
+
}
|
|
129
|
+
if (isProcessRequest(request)) {
|
|
130
|
+
return spawnProcessHandle(request);
|
|
131
|
+
}
|
|
132
|
+
throw new Error('Unsupported subprocess request variant.');
|
|
66
133
|
},
|
|
67
134
|
async get(handleId) {
|
|
68
135
|
return handles.get(handleId) ?? null;
|
|
@@ -76,6 +143,7 @@ export function createInMemorySubprocessAdapter(options = {}) {
|
|
|
76
143
|
};
|
|
77
144
|
}
|
|
78
145
|
active.delete(handleId);
|
|
146
|
+
await clearIfDurable(handleId);
|
|
79
147
|
const next = await save({
|
|
80
148
|
...handle,
|
|
81
149
|
status: 'stopped',
|
|
@@ -122,5 +190,46 @@ export function createInMemorySubprocessAdapter(options = {}) {
|
|
|
122
190
|
async isAlive(handle) {
|
|
123
191
|
return active.has(handle.id);
|
|
124
192
|
},
|
|
193
|
+
/**
|
|
194
|
+
* Idempotent re-run of a persisted step, not a "resume" — for in-memory
|
|
195
|
+
* there is no long-lived process to bind back to. The adapter's
|
|
196
|
+
* `reattachMode` on the manifest reflects this honestly. When no
|
|
197
|
+
* storage was configured we fall back to the ephemeral semantics
|
|
198
|
+
* established in Phase A (returning `null`).
|
|
199
|
+
*/
|
|
200
|
+
async reattach(handleId) {
|
|
201
|
+
if (!storage) {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
const manifest = await loadManifest(storage, handleId);
|
|
205
|
+
if (!manifest) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const handle = handleFromManifest(manifest);
|
|
209
|
+
active.add(handle.id);
|
|
210
|
+
await save(handle);
|
|
211
|
+
return handle;
|
|
212
|
+
},
|
|
213
|
+
async listLive() {
|
|
214
|
+
const live = new Map();
|
|
215
|
+
for (const id of active) {
|
|
216
|
+
const handle = handles.get(id);
|
|
217
|
+
if (handle) {
|
|
218
|
+
live.set(handle.id, handle);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (storage) {
|
|
222
|
+
const manifests = await listManifests(storage);
|
|
223
|
+
for (const manifest of manifests) {
|
|
224
|
+
if (!live.has(manifest.handleId)) {
|
|
225
|
+
live.set(manifest.handleId, handleFromManifest(manifest));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return [
|
|
230
|
+
...live.values(),
|
|
231
|
+
];
|
|
232
|
+
},
|
|
125
233
|
};
|
|
126
234
|
}
|
|
235
|
+
//#endregion
|