@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 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, agent, tools, workspace } from "@trama-dev/runtime";
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, and [docs/workspace-rfc.md](https://github.com/NaNhkNaN/trama/blob/main/docs/workspace-rfc.md) for the design rationale.
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 path. */
30
- readonly workspace: string;
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 { mkdirSync, existsSync, mkdtempSync } from "fs";
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 path. */
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(workspace) {
9
- this.workspace = workspace;
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
- const workspace = resolve(options.workspace);
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
- const workspace = mkdtempSync(resolve(tmpdir(), prefix));
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 { projectDir, args, timeout, maxRepairAttempts } = options;
37
- const workspaceDir = this.workspace;
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
- // Track participant lifecycle state
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 promise = runProgram({
50
- projectDir,
51
- timeout,
52
- maxRepairAttempts,
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
- // Start the run immediately
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-fast: check current state synchronously — don't block on a running participant
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
- const mergedArgs = newArgs ? { ...(lastArgs ?? {}), ...newArgs } : lastArgs;
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.1.5",
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.1.5"
36
+ "@trama-dev/runtime": "0.2.0"
34
37
  },
35
38
  "devDependencies": {
36
- "@types/node": "^25.5.0"
39
+ "@types/node": "^26.6.6"
37
40
  }
38
41
  }