@ours.network/fleet 0.9.3 → 0.9.5

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.
@@ -0,0 +1,47 @@
1
+ import type { SessionBackendId } from '../config.js';
2
+ export type SessionReadiness = 'starting' | 'idle' | 'running' | 'awaiting_permission' | 'failed';
3
+ export interface TurnResult {
4
+ accepted: boolean;
5
+ outcome: 'completed' | 'refused' | 'cancelled' | 'failed' | 'inconclusive';
6
+ detail?: string;
7
+ }
8
+ export interface SessionSnapshot {
9
+ backend: SessionBackendId;
10
+ alive: boolean;
11
+ readiness: SessionReadiness;
12
+ sessionId?: string;
13
+ lastError?: string;
14
+ pendingPermissionId?: string;
15
+ }
16
+ export type SessionEventKind = 'state' | 'agent_text' | 'thought' | 'tool_call' | 'tool_update' | 'permission' | 'turn_stop' | 'error';
17
+ export interface SessionEvent {
18
+ version: 1;
19
+ seq: number;
20
+ at: string;
21
+ kind: SessionEventKind;
22
+ turnId?: string;
23
+ toolCallId?: string;
24
+ permissionId?: string;
25
+ text?: string;
26
+ title?: string;
27
+ status?: string;
28
+ stopReason?: string;
29
+ options?: Array<{
30
+ optionId: string;
31
+ name: string;
32
+ kind: string;
33
+ }>;
34
+ }
35
+ export interface SessionHandle {
36
+ readonly backend: SessionBackendId;
37
+ readonly pid: number;
38
+ isAlive(): boolean;
39
+ snapshot(): SessionSnapshot;
40
+ submitPrompt(text: string): Promise<TurnResult>;
41
+ interrupt(): Promise<void>;
42
+ respondPermission(permissionId: string, optionId: string): boolean;
43
+ eventsSince(seq: number): SessionEvent[];
44
+ subscribe(listener: (event: SessionEvent) => void): () => void;
45
+ setControllerAttached(attached: boolean): void;
46
+ close(): Promise<void>;
47
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/spawn.d.ts CHANGED
@@ -1,14 +1,19 @@
1
+ import { type ApprovalMode, type FilesystemMode, type SessionBackendId, type UnattendedMode } from './config.js';
1
2
  import { type OpsDeps } from './ops.js';
2
3
  export interface SpawnOpts {
3
4
  name: string;
4
5
  temp?: boolean;
5
6
  harness?: string;
7
+ session?: SessionBackendId;
6
8
  mission?: string;
7
9
  identity?: string;
8
10
  cwd?: string;
9
11
  coordinator?: string;
10
12
  model?: string;
11
13
  permissionMode?: string;
14
+ approval?: ApprovalMode;
15
+ filesystem?: FilesystemMode;
16
+ unattended?: UnattendedMode;
12
17
  sandbox?: string;
13
18
  profile?: string;
14
19
  launcher?: string;
package/dist/spawn.js CHANGED
@@ -3,13 +3,15 @@ import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from 'no
3
3
  import { join } from 'node:path';
4
4
  import { stringify } from 'yaml';
5
5
  import { agentDir, fleetDDir } from './paths.js';
6
- import { loadConfig, resolveMonitorConfig } from './config.js';
6
+ import { loadConfig, resolveMonitorConfig, resolvePermissions, } from './config.js';
7
7
  import { applyRole, up } from './ops.js';
8
8
  import { START_STAGGER_FILE } from './runner.js';
9
9
  function roleFromOpts(o, defaultHarness) {
10
10
  const r = {};
11
11
  if (o.harness)
12
12
  r.harness = o.harness;
13
+ if (o.session)
14
+ r.session = o.session;
13
15
  if (o.identity)
14
16
  r.identity = o.identity;
15
17
  if (o.cwd)
@@ -40,12 +42,29 @@ function roleFromOpts(o, defaultHarness) {
40
42
  harnessOptions.monitor = true;
41
43
  if (Object.keys(harnessOptions).length)
42
44
  r.harness_options = harnessOptions;
45
+ if (o.approval || o.filesystem || o.unattended) {
46
+ r.permissions = {
47
+ ...(o.approval ? { approval: o.approval } : {}),
48
+ ...(o.filesystem ? { filesystem: o.filesystem } : {}),
49
+ ...(o.unattended ? { unattended: o.unattended } : {}),
50
+ };
51
+ }
43
52
  if (o.bioFile)
44
53
  r.bio = readFileSync(o.bioFile, 'utf8').trim();
45
54
  if (o.personaFile)
46
55
  r.persona = readFileSync(o.personaFile, 'utf8').trim();
47
56
  return r;
48
57
  }
58
+ function validateSpawnOpts(o) {
59
+ if (o.session && !['tmux', 'acp'].includes(o.session))
60
+ throw new Error(`invalid --session '${o.session}'; allowed: tmux, acp`);
61
+ if (o.approval && !['ask', 'allow', 'deny'].includes(o.approval))
62
+ throw new Error(`invalid --approval '${o.approval}'; allowed: ask, allow, deny`);
63
+ if (o.filesystem && !['read-only', 'workspace', 'unrestricted'].includes(o.filesystem))
64
+ throw new Error(`invalid --filesystem '${o.filesystem}'; allowed: read-only, workspace, unrestricted`);
65
+ if (o.unattended && !['deny', 'wait'].includes(o.unattended))
66
+ throw new Error(`invalid --unattended '${o.unattended}'; allowed: deny, wait`);
67
+ }
49
68
  function assertNameFree(o) {
50
69
  const cfg = loadConfig(o.configPath);
51
70
  if (cfg.roles.some(r => r.name === o.name))
@@ -55,6 +74,7 @@ function assertNameFree(o) {
55
74
  }
56
75
  /** Permanent spawn: persist to ~/fleet.d/<Name>.yaml, then bring it up. */
57
76
  export async function spawnPermanent(o, deps) {
77
+ validateSpawnOpts(o);
58
78
  assertNameFree(o);
59
79
  const cfg = loadConfig(o.configPath);
60
80
  mkdirSync(fleetDDir(), { recursive: true });
@@ -76,6 +96,7 @@ const detachedSupervisor = (binPath, args, dir) => {
76
96
  };
77
97
  /** Temp spawn: state under ~/.ours-fleet/tmp, plain tmux, auto-clean on exit. */
78
98
  export async function spawnTemp(o, binPath, launch = detachedSupervisor) {
99
+ validateSpawnOpts(o);
79
100
  assertNameFree(o);
80
101
  const cfg = loadConfig(o.configPath);
81
102
  const defaultHarness = cfg.defaults.harness;
@@ -88,9 +109,11 @@ export async function spawnTemp(o, binPath, launch = detachedSupervisor) {
88
109
  ...fromOpts,
89
110
  name: o.name,
90
111
  harness: o.harness ?? defaultHarness ?? 'claude-code',
112
+ session: o.session ?? cfg.defaults.session ?? 'tmux',
91
113
  identity: o.identity ?? o.name,
92
114
  model: o.model?.trim() || cfg.defaults.model,
93
115
  harness_options: Object.keys(mergedHarnessOptions).length ? mergedHarnessOptions : undefined,
116
+ permissions: resolvePermissions(cfg.defaults.permissions, fromOpts.permissions),
94
117
  // Temp agents inherit the fleet-wide monitor defaults via the snapshot (design §2).
95
118
  monitor: resolveMonitorConfig(cfg.defaults.monitor, fromOpts.monitor),
96
119
  sourceFile: '(temp)',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ours.network/fleet",
3
- "version": "0.9.3",
4
- "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux consoles, systemd/launchd supervision, ours.network messaging.",
3
+ "version": "0.9.5",
4
+ "description": "Harness-agnostic fleet of persistent, identity-bound AI agents. Declarative fleet.yaml, tmux or ACP sessions, supervision, and ours.network messaging.",
5
5
  "type": "module",
6
6
  "license": "FSL-1.1-Apache-2.0",
7
7
  "repository": {
@@ -26,9 +26,14 @@
26
26
  "prepublishOnly": "npm run build && npm test"
27
27
  },
28
28
  "dependencies": {
29
+ "@agentclientprotocol/sdk": "^1.3.0",
29
30
  "commander": "^12.1.0",
30
31
  "yaml": "^2.5.0"
31
32
  },
33
+ "optionalDependencies": {
34
+ "@agentclientprotocol/claude-agent-acp": "^0.63.0",
35
+ "@agentclientprotocol/codex-acp": "^1.1.7"
36
+ },
32
37
  "devDependencies": {
33
38
  "@types/node": "^20.14.0",
34
39
  "typescript": "^5.5.0",