@trama-dev/session 0.1.5 → 0.2.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 +7 -3
- package/dist/index.d.ts +12 -6
- package/dist/index.js +36 -39
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ The session layer for [trama](https://github.com/NaNhkNaN/trama) — shared work
|
|
|
7
7
|
## Usage
|
|
8
8
|
|
|
9
9
|
```typescript
|
|
10
|
-
import { ctx
|
|
10
|
+
import { ctx } from "@trama-dev/runtime";
|
|
11
11
|
import { Session } from "@trama-dev/session";
|
|
12
12
|
|
|
13
13
|
const session = await Session.createTemp();
|
|
@@ -17,6 +17,10 @@ const handle = session.spawn("check-sql", {
|
|
|
17
17
|
projectDir: "~/.trama/projects/check-sql",
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
+
// Take part in the shared workspace yourself
|
|
21
|
+
await session.workspace.write("diff.txt", diff);
|
|
22
|
+
const findings = await session.workspace.observe("findings/*.json", { expect: 1 });
|
|
23
|
+
|
|
20
24
|
const result = await handle.wait();
|
|
21
25
|
// { status: "done" | "yielded" | "failed", reason?, result? }
|
|
22
26
|
|
|
@@ -29,7 +33,7 @@ if (result.status === "yielded") {
|
|
|
29
33
|
|
|
30
34
|
## What the session layer provides
|
|
31
35
|
|
|
32
|
-
- **Shared workspace** — participants read and write artifacts through a common directory with atomic writes and path guarding.
|
|
36
|
+
- **Shared workspace** — participants read and write artifacts through a common directory with atomic writes and path guarding. Participants import it as `workspace` from `@trama-dev/runtime`; the program that created the session uses `session.workspace`.
|
|
33
37
|
- **Structured results** — `ProgramResult` with `status`, `reason`, and `result` fields. The session layer never reads `state.json` directly.
|
|
34
38
|
- **Yield/resume** — participants can suspend cooperatively via `ctx.yield()` and be resumed with new args.
|
|
35
39
|
- **Failure isolation** — each participant has its own trama lifecycle, repair loop, and state. A crashing participant doesn't corrupt the workspace or block others.
|
|
@@ -38,4 +42,4 @@ if (result.status === "yielded") {
|
|
|
38
42
|
|
|
39
43
|
The session layer is **policy**. The runtime is **mechanism**. The session manages coordination (who participates, when to resume). The runtime manages execution (state persistence, auto-repair, version history). This boundary is enforced: `session` depends on `runtime`, never the reverse.
|
|
40
44
|
|
|
41
|
-
See the [main README](https://github.com/NaNhkNaN/trama) for the full API reference
|
|
45
|
+
See the [main README](https://github.com/NaNhkNaN/trama) for the full API reference.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { ProgramResult } from "@trama-dev/runtime/runner";
|
|
2
|
-
export type { ProgramResult };
|
|
1
|
+
import type { ProgramResult, Workspace } from "@trama-dev/runtime/runner";
|
|
2
|
+
export type { ProgramResult, Workspace };
|
|
3
3
|
export interface SessionOptions {
|
|
4
|
-
/** Directory for shared workspace artifacts. Created if it doesn't exist. */
|
|
4
|
+
/** Directory for shared workspace artifacts. Created if it doesn't exist. A leading `~` is expanded. */
|
|
5
5
|
workspace: string;
|
|
6
6
|
}
|
|
7
7
|
export interface SpawnOptions {
|
|
8
|
-
/** Path to the trama project directory. */
|
|
8
|
+
/** Path to the trama project directory. A leading `~` is expanded. */
|
|
9
9
|
projectDir: string;
|
|
10
10
|
/** Arguments passed to the program via ctx.input.args. */
|
|
11
11
|
args?: Record<string, unknown>;
|
|
@@ -26,8 +26,14 @@ export interface ParticipantHandle {
|
|
|
26
26
|
resume(args?: Record<string, unknown>): Promise<ParticipantHandle>;
|
|
27
27
|
}
|
|
28
28
|
export declare class Session {
|
|
29
|
-
/** The shared workspace directory
|
|
30
|
-
readonly
|
|
29
|
+
/** The shared workspace directory. */
|
|
30
|
+
readonly workspaceDir: string;
|
|
31
|
+
/**
|
|
32
|
+
* The shared workspace itself — the same read / write / list / observe that
|
|
33
|
+
* participants get as `workspace` from "@trama-dev/runtime". This is how the
|
|
34
|
+
* program that creates the session takes part in it.
|
|
35
|
+
*/
|
|
36
|
+
readonly workspace: Workspace;
|
|
31
37
|
private constructor();
|
|
32
38
|
/**
|
|
33
39
|
* Create a new session with a shared workspace directory.
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { resolve } from "path";
|
|
3
|
-
import { tmpdir } from "os";
|
|
4
|
-
import { runProgram } from "@trama-dev/runtime/runner";
|
|
1
|
+
import { existsSync, mkdtempSync } from "fs";
|
|
2
|
+
import { join, resolve } from "path";
|
|
3
|
+
import { homedir, tmpdir } from "os";
|
|
4
|
+
import { createWorkspace, runProgram } from "@trama-dev/runtime/runner";
|
|
5
|
+
/** Make a path absolute, expanding a leading `~` to the user's home directory. */
|
|
6
|
+
function toAbsolute(path) {
|
|
7
|
+
if (path === "~")
|
|
8
|
+
return homedir();
|
|
9
|
+
if (path.startsWith("~/"))
|
|
10
|
+
return join(homedir(), path.slice(2));
|
|
11
|
+
return resolve(path);
|
|
12
|
+
}
|
|
5
13
|
export class Session {
|
|
6
|
-
/** The shared workspace directory
|
|
14
|
+
/** The shared workspace directory. */
|
|
15
|
+
workspaceDir;
|
|
16
|
+
/**
|
|
17
|
+
* The shared workspace itself — the same read / write / list / observe that
|
|
18
|
+
* participants get as `workspace` from "@trama-dev/runtime". This is how the
|
|
19
|
+
* program that creates the session takes part in it.
|
|
20
|
+
*/
|
|
7
21
|
workspace;
|
|
8
|
-
constructor(
|
|
9
|
-
this.
|
|
22
|
+
constructor(workspaceDir) {
|
|
23
|
+
this.workspaceDir = workspaceDir;
|
|
24
|
+
this.workspace = createWorkspace(workspaceDir);
|
|
10
25
|
}
|
|
11
26
|
/**
|
|
12
27
|
* Create a new session with a shared workspace directory.
|
|
13
28
|
* The workspace is created if it doesn't exist.
|
|
14
29
|
*/
|
|
15
30
|
static async create(options) {
|
|
16
|
-
|
|
17
|
-
mkdirSync(workspace, { recursive: true });
|
|
18
|
-
return new Session(workspace);
|
|
31
|
+
return new Session(toAbsolute(options.workspace));
|
|
19
32
|
}
|
|
20
33
|
/**
|
|
21
34
|
* Create a new session with a temporary workspace directory.
|
|
@@ -25,60 +38,44 @@ export class Session {
|
|
|
25
38
|
if (prefix.includes("/") || prefix.includes("\\")) {
|
|
26
39
|
throw new Error("createTemp prefix must not contain path separators");
|
|
27
40
|
}
|
|
28
|
-
|
|
29
|
-
return new Session(workspace);
|
|
41
|
+
return new Session(mkdtempSync(resolve(tmpdir(), prefix)));
|
|
30
42
|
}
|
|
31
43
|
/**
|
|
32
44
|
* Spawn a trama program as a session participant.
|
|
33
45
|
* The program gets access to the shared workspace via the workspace API.
|
|
34
46
|
*/
|
|
35
47
|
spawn(name, options) {
|
|
36
|
-
const {
|
|
37
|
-
const
|
|
48
|
+
const { args, timeout, maxRepairAttempts } = options;
|
|
49
|
+
const projectDir = toAbsolute(options.projectDir);
|
|
50
|
+
const workspaceDir = this.workspaceDir;
|
|
38
51
|
if (!existsSync(projectDir)) {
|
|
39
52
|
throw new Error(`Project directory not found: ${projectDir}`);
|
|
40
53
|
}
|
|
41
|
-
//
|
|
54
|
+
// Where the participant is in its lifecycle, and the args of its latest run —
|
|
55
|
+
// a resume merges new args over them, so they accumulate across yields.
|
|
42
56
|
let state = "running";
|
|
43
|
-
let resultPromise;
|
|
44
|
-
// Track the latest effective args so multi-yield resumes accumulate state
|
|
45
57
|
let lastArgs = args;
|
|
46
58
|
const startRun = (runArgs) => {
|
|
47
59
|
state = "running";
|
|
48
60
|
lastArgs = runArgs;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
args: runArgs ?? args,
|
|
54
|
-
workspaceDir,
|
|
55
|
-
});
|
|
56
|
-
// Track state transitions when the run completes
|
|
57
|
-
promise.then((result) => {
|
|
58
|
-
state = result.status === "yielded" ? "yielded"
|
|
59
|
-
: result.status === "done" ? "done"
|
|
60
|
-
: "failed";
|
|
61
|
-
}, () => {
|
|
62
|
-
// Infrastructure error — treat as failed
|
|
63
|
-
state = "failed";
|
|
64
|
-
});
|
|
65
|
-
return promise;
|
|
61
|
+
const run = runProgram({ projectDir, timeout, maxRepairAttempts, args: runArgs, workspaceDir });
|
|
62
|
+
// An infrastructure error (the run could not happen at all) counts as failed.
|
|
63
|
+
run.then(result => { state = result.status; }, () => { state = "failed"; });
|
|
64
|
+
return run;
|
|
66
65
|
};
|
|
67
|
-
|
|
68
|
-
resultPromise = startRun(args);
|
|
66
|
+
let resultPromise = startRun(args);
|
|
69
67
|
const handle = {
|
|
70
68
|
name,
|
|
71
69
|
wait() {
|
|
72
70
|
return resultPromise;
|
|
73
71
|
},
|
|
74
72
|
async resume(newArgs) {
|
|
75
|
-
// Fail
|
|
73
|
+
// Fail fast rather than wait on a participant that is still running.
|
|
76
74
|
if (state !== "yielded") {
|
|
77
75
|
throw new Error(`Cannot resume participant "${name}": expected state "yielded" but got "${state}". ` +
|
|
78
76
|
`resume() is only valid after a program calls ctx.yield().`);
|
|
79
77
|
}
|
|
80
|
-
|
|
81
|
-
resultPromise = startRun(mergedArgs);
|
|
78
|
+
resultPromise = startRun(newArgs ? { ...lastArgs, ...newArgs } : lastArgs);
|
|
82
79
|
return handle;
|
|
83
80
|
},
|
|
84
81
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trama-dev/session",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -29,10 +29,13 @@
|
|
|
29
29
|
"LICENSE"
|
|
30
30
|
],
|
|
31
31
|
"os": ["darwin", "linux"],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22.19.0"
|
|
34
|
+
},
|
|
32
35
|
"dependencies": {
|
|
33
|
-
"@trama-dev/runtime": "0.
|
|
36
|
+
"@trama-dev/runtime": "0.2.0"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|
|
36
|
-
"@types/node": "^
|
|
39
|
+
"@types/node": "^26.6.6"
|
|
37
40
|
}
|
|
38
41
|
}
|