@love-moon/conductor-sdk 0.7.5 → 0.7.7
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/CHANGELOG.md +23 -0
- package/dist/client.js +1 -1
- package/dist/config/index.js +2 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/outbox/store.d.ts +24 -0
- package/dist/outbox/store.js +44 -0
- package/dist/paths.d.ts +7 -0
- package/dist/paths.js +39 -0
- package/dist/session/store.d.ts +4 -2
- package/dist/session/store.js +8 -6
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @love-moon/conductor-sdk
|
|
2
2
|
|
|
3
|
+
## 0.7.7
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 400f3a7: Report a terminal task status when a stop request finds no active process, so a
|
|
8
|
+
task whose Fire already died converges instead of sitting in `killing` forever.
|
|
9
|
+
|
|
10
|
+
Drop queued terminal status events before an in-place restart reuses a working
|
|
11
|
+
directory. The durable upstream outbox lives inside that directory, so an
|
|
12
|
+
undelivered `KILLED` from the previous run was flushed on startup and killed the
|
|
13
|
+
task that had just finished resuming.
|
|
14
|
+
|
|
15
|
+
## 0.7.6
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- 7bbb412: Add `CONDUCTOR_HOME` support for relocating user-level configuration, logs,
|
|
20
|
+
Fire locks, sessions, update metadata, and AI manager caches while leaving
|
|
21
|
+
project-scoped `.conductor` directories and Fire task markers in place.
|
|
22
|
+
|
|
23
|
+
Migrate device authorization to `conductor.conductor-ai.top` while preserving
|
|
24
|
+
compatibility with the legacy official endpoint and self-hosted backends.
|
|
25
|
+
|
|
3
26
|
## 0.7.5
|
|
4
27
|
|
|
5
28
|
## 0.7.4
|
package/dist/client.js
CHANGED
|
@@ -54,7 +54,7 @@ export class ConductorClient {
|
|
|
54
54
|
const projectPath = options.projectPath ?? process.cwd();
|
|
55
55
|
const backendApi = options.backendApi ?? new BackendApiClient(config);
|
|
56
56
|
const sessions = options.sessionManager ?? new SessionManager();
|
|
57
|
-
const sessionStore = options.sessionStore ?? SessionDiskStore.forBackendUrl(config.backendUrl);
|
|
57
|
+
const sessionStore = options.sessionStore ?? SessionDiskStore.forBackendUrl(config.backendUrl, env);
|
|
58
58
|
const messageRouter = options.messageRouter ?? new MessageRouter(sessions);
|
|
59
59
|
const agentHost = resolveAgentHost(env, options.agentHost);
|
|
60
60
|
const deliveryScopeId = normalizeDeliveryScopeId(options.deliveryScopeId ?? `agent:${agentHost}`);
|
package/dist/config/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
2
|
import yaml from 'yaml';
|
|
3
|
+
import { resolveConductorConfigPath } from '../paths.js';
|
|
5
4
|
export const CONFIG_ENV_VAR = 'CONDUCTOR_CONFIG';
|
|
6
5
|
export const AGENT_TOKEN_ENV_VAR = 'CONDUCTOR_AGENT_TOKEN';
|
|
7
6
|
export const BACKEND_URL_ENV_VAR = 'CONDUCTOR_BACKEND_URL';
|
|
8
7
|
export const WS_URL_ENV_VAR = 'CONDUCTOR_WS_URL';
|
|
9
8
|
export const LOG_LEVEL_ENV_VAR = 'CONDUCTOR_LOG_LEVEL';
|
|
10
|
-
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), '.conductor', 'config.yaml');
|
|
11
9
|
const ALLOWED_LOG_LEVELS = new Set(['debug', 'info', 'warning', 'error', 'critical']);
|
|
12
10
|
export class ConfigError extends Error {
|
|
13
11
|
}
|
|
@@ -97,21 +95,12 @@ export function loadConfig(targetPath, options = {}) {
|
|
|
97
95
|
});
|
|
98
96
|
}
|
|
99
97
|
function resolveConfigPath(explicitPath, env) {
|
|
100
|
-
const
|
|
101
|
-
env[CONFIG_ENV_VAR] ||
|
|
102
|
-
DEFAULT_CONFIG_PATH;
|
|
103
|
-
const normalized = normalizePath(candidate);
|
|
98
|
+
const normalized = resolveConductorConfigPath(explicitPath, env);
|
|
104
99
|
if (!fs.existsSync(normalized)) {
|
|
105
100
|
throw new ConfigFileNotFound(normalized);
|
|
106
101
|
}
|
|
107
102
|
return normalized;
|
|
108
103
|
}
|
|
109
|
-
function normalizePath(value) {
|
|
110
|
-
const expanded = value.startsWith('~')
|
|
111
|
-
? path.join(os.homedir(), value.slice(1))
|
|
112
|
-
: value;
|
|
113
|
-
return path.resolve(expanded);
|
|
114
|
-
}
|
|
115
104
|
function readYaml(filePath) {
|
|
116
105
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
117
106
|
let parsed;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/outbox/store.d.ts
CHANGED
|
@@ -21,6 +21,30 @@ export declare class DurableUpstreamOutboxStore {
|
|
|
21
21
|
payload: Record<string, unknown>;
|
|
22
22
|
}): DurableUpstreamEvent;
|
|
23
23
|
remove(stableId: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* Discard queued terminal `task_status_update` events (KILLED / COMPLETED).
|
|
26
|
+
*
|
|
27
|
+
* This outbox lives INSIDE the fire's working directory, and an in-place
|
|
28
|
+
* restart deliberately re-uses that same directory (the daemon sets
|
|
29
|
+
* `CONDUCTOR_RESUME_CWD` so `--resume` finds the original project). That
|
|
30
|
+
* means a terminal status the PREVIOUS run failed to deliver — e.g. its
|
|
31
|
+
* websocket dropped while it was being stopped — is still sitting on disk
|
|
32
|
+
* when the NEXT run starts, and the startup flush delivers it against the
|
|
33
|
+
* task that was just brought back to life. The server marks the task
|
|
34
|
+
* `killed`, then answers the newly-attached fire with
|
|
35
|
+
* `stop_task(task_already_killed)` and shoots it down seconds after it
|
|
36
|
+
* finished resuming.
|
|
37
|
+
*
|
|
38
|
+
* A terminal status from a superseded run is definitionally obsolete: the
|
|
39
|
+
* task has since been restarted, so "that run ended" is no longer news the
|
|
40
|
+
* server can act on correctly. Drop those and keep everything else —
|
|
41
|
+
* `sdk_message` entries are real conversation content that must still be
|
|
42
|
+
* delivered, and `task_stop_ack` / `agent_command_ack` are idempotent
|
|
43
|
+
* bookkeeping whose delivery actually clears stale server-side outbox rows.
|
|
44
|
+
*
|
|
45
|
+
* Returns the dropped entries so callers can log exactly what was discarded.
|
|
46
|
+
*/
|
|
47
|
+
dropPendingTerminalStatusEvents(): DurableUpstreamEvent[];
|
|
24
48
|
markRetry(stableId: string, delayMs: number): DurableUpstreamEvent | null;
|
|
25
49
|
listReady(nowMs?: number): DurableUpstreamEvent[];
|
|
26
50
|
nextRetryDelay(nowMs?: number): number | null;
|
package/dist/outbox/store.js
CHANGED
|
@@ -19,6 +19,16 @@ const sleepSync = (ms) => {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
|
+
// Terminal statuses as published by `conductor daemon` (see cli/src/daemon.js,
|
|
23
|
+
// which sends the upper-case wire form). Compared case-insensitively so a
|
|
24
|
+
// future lower-case producer does not silently bypass the check.
|
|
25
|
+
const TERMINAL_TASK_STATUSES = new Set(['killed', 'completed']);
|
|
26
|
+
const isTerminalStatusEvent = (entry) => {
|
|
27
|
+
if (entry.eventType !== 'task_status_update')
|
|
28
|
+
return false;
|
|
29
|
+
const status = entry.payload?.status;
|
|
30
|
+
return typeof status === 'string' && TERMINAL_TASK_STATUSES.has(status.trim().toLowerCase());
|
|
31
|
+
};
|
|
22
32
|
const sanitizeScopeId = (scopeId) => scopeId
|
|
23
33
|
.trim()
|
|
24
34
|
.replace(/[^a-zA-Z0-9._-]+/g, '_')
|
|
@@ -66,6 +76,40 @@ export class DurableUpstreamOutboxStore {
|
|
|
66
76
|
this.saveUnlocked(entries);
|
|
67
77
|
});
|
|
68
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Discard queued terminal `task_status_update` events (KILLED / COMPLETED).
|
|
81
|
+
*
|
|
82
|
+
* This outbox lives INSIDE the fire's working directory, and an in-place
|
|
83
|
+
* restart deliberately re-uses that same directory (the daemon sets
|
|
84
|
+
* `CONDUCTOR_RESUME_CWD` so `--resume` finds the original project). That
|
|
85
|
+
* means a terminal status the PREVIOUS run failed to deliver — e.g. its
|
|
86
|
+
* websocket dropped while it was being stopped — is still sitting on disk
|
|
87
|
+
* when the NEXT run starts, and the startup flush delivers it against the
|
|
88
|
+
* task that was just brought back to life. The server marks the task
|
|
89
|
+
* `killed`, then answers the newly-attached fire with
|
|
90
|
+
* `stop_task(task_already_killed)` and shoots it down seconds after it
|
|
91
|
+
* finished resuming.
|
|
92
|
+
*
|
|
93
|
+
* A terminal status from a superseded run is definitionally obsolete: the
|
|
94
|
+
* task has since been restarted, so "that run ended" is no longer news the
|
|
95
|
+
* server can act on correctly. Drop those and keep everything else —
|
|
96
|
+
* `sdk_message` entries are real conversation content that must still be
|
|
97
|
+
* delivered, and `task_stop_ack` / `agent_command_ack` are idempotent
|
|
98
|
+
* bookkeeping whose delivery actually clears stale server-side outbox rows.
|
|
99
|
+
*
|
|
100
|
+
* Returns the dropped entries so callers can log exactly what was discarded.
|
|
101
|
+
*/
|
|
102
|
+
dropPendingTerminalStatusEvents() {
|
|
103
|
+
return this.withLock(() => {
|
|
104
|
+
const entries = this.loadUnlocked();
|
|
105
|
+
const dropped = entries.filter(isTerminalStatusEvent);
|
|
106
|
+
if (dropped.length === 0) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
this.saveUnlocked(entries.filter((entry) => !isTerminalStatusEvent(entry)));
|
|
110
|
+
return dropped;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
69
113
|
markRetry(stableId, delayMs) {
|
|
70
114
|
return this.withLock(() => {
|
|
71
115
|
const entries = this.loadUnlocked();
|
package/dist/paths.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const CONDUCTOR_HOME_ENV_VAR = "CONDUCTOR_HOME";
|
|
2
|
+
export declare const CONDUCTOR_CONFIG_ENV_VAR = "CONDUCTOR_CONFIG";
|
|
3
|
+
export type ConductorPathEnv = Record<string, string | undefined>;
|
|
4
|
+
export declare function resolveUserHome(env?: ConductorPathEnv, fallbackHome?: string): string;
|
|
5
|
+
export declare function resolveHomeRelativePath(value: string, env?: ConductorPathEnv, fallbackHome?: string): string;
|
|
6
|
+
export declare function resolveConductorHome(env?: ConductorPathEnv): string;
|
|
7
|
+
export declare function resolveConductorConfigPath(explicitPath?: string, env?: ConductorPathEnv): string;
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const CONDUCTOR_HOME_ENV_VAR = 'CONDUCTOR_HOME';
|
|
4
|
+
export const CONDUCTOR_CONFIG_ENV_VAR = 'CONDUCTOR_CONFIG';
|
|
5
|
+
function normalizeOptionalString(value) {
|
|
6
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
7
|
+
}
|
|
8
|
+
export function resolveUserHome(env = process.env, fallbackHome = os.homedir()) {
|
|
9
|
+
return normalizeOptionalString(env.HOME)
|
|
10
|
+
|| normalizeOptionalString(env.USERPROFILE)
|
|
11
|
+
|| normalizeOptionalString(fallbackHome)
|
|
12
|
+
|| '/tmp';
|
|
13
|
+
}
|
|
14
|
+
export function resolveHomeRelativePath(value, env = process.env, fallbackHome = os.homedir()) {
|
|
15
|
+
const normalized = normalizeOptionalString(value);
|
|
16
|
+
const userHome = resolveUserHome(env, fallbackHome);
|
|
17
|
+
if (normalized === '~') {
|
|
18
|
+
return path.resolve(userHome);
|
|
19
|
+
}
|
|
20
|
+
if (normalized.startsWith('~/') || normalized.startsWith('~\\')) {
|
|
21
|
+
return path.resolve(userHome, normalized.slice(2));
|
|
22
|
+
}
|
|
23
|
+
return path.resolve(normalized);
|
|
24
|
+
}
|
|
25
|
+
export function resolveConductorHome(env = process.env) {
|
|
26
|
+
const configuredHome = normalizeOptionalString(env[CONDUCTOR_HOME_ENV_VAR]);
|
|
27
|
+
if (configuredHome) {
|
|
28
|
+
return resolveHomeRelativePath(configuredHome, env);
|
|
29
|
+
}
|
|
30
|
+
return path.join(path.resolve(resolveUserHome(env)), '.conductor');
|
|
31
|
+
}
|
|
32
|
+
export function resolveConductorConfigPath(explicitPath, env = process.env) {
|
|
33
|
+
const configuredPath = normalizeOptionalString(explicitPath)
|
|
34
|
+
|| normalizeOptionalString(env[CONDUCTOR_CONFIG_ENV_VAR]);
|
|
35
|
+
if (configuredPath) {
|
|
36
|
+
return resolveHomeRelativePath(configuredPath, env);
|
|
37
|
+
}
|
|
38
|
+
return path.join(resolveConductorHome(env), 'config.yaml');
|
|
39
|
+
}
|
package/dist/session/store.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ConductorPathEnv } from '../paths.js';
|
|
1
2
|
export declare const DEFAULT_SESSION_DIR: string;
|
|
2
3
|
export declare const DEFAULT_SESSION_PATH: string;
|
|
3
4
|
export declare const DEFAULT_SESSION_ENV = "CODEX_SESSION_ID";
|
|
@@ -29,9 +30,10 @@ export declare class SessionDiskStore {
|
|
|
29
30
|
constructor(filePath?: string);
|
|
30
31
|
/**
|
|
31
32
|
* Create a SessionDiskStore for a specific backend URL.
|
|
32
|
-
* Sessions are stored in
|
|
33
|
+
* Sessions are stored in $CONDUCTOR_HOME/sessions/<host>.yaml.
|
|
34
|
+
* When CONDUCTOR_HOME is unset, it defaults to ~/.conductor.
|
|
33
35
|
*/
|
|
34
|
-
static forBackendUrl(backendUrl: string): SessionDiskStore;
|
|
36
|
+
static forBackendUrl(backendUrl: string, env?: ConductorPathEnv): SessionDiskStore;
|
|
35
37
|
load(): SessionRecord[];
|
|
36
38
|
private loadUnlocked;
|
|
37
39
|
save(records: SessionRecord[]): void;
|
package/dist/session/store.js
CHANGED
|
@@ -2,8 +2,9 @@ import fs from 'node:fs';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
|
-
|
|
6
|
-
export const
|
|
5
|
+
import { resolveConductorHome } from '../paths.js';
|
|
6
|
+
export const DEFAULT_SESSION_DIR = path.join(resolveConductorHome(), 'sessions');
|
|
7
|
+
export const DEFAULT_SESSION_PATH = path.join(resolveConductorHome(), 'session.yaml');
|
|
7
8
|
export const DEFAULT_SESSION_ENV = 'CODEX_SESSION_ID';
|
|
8
9
|
export const DEFAULT_SESSION_FALLBACK_ENV = 'SESSION_ID';
|
|
9
10
|
const SESSION_LOCK_TIMEOUT_MS = 10_000;
|
|
@@ -81,17 +82,18 @@ export class SessionRecord {
|
|
|
81
82
|
export class SessionDiskStore {
|
|
82
83
|
filePath;
|
|
83
84
|
lockPath;
|
|
84
|
-
constructor(filePath =
|
|
85
|
+
constructor(filePath = path.join(resolveConductorHome(), 'session.yaml')) {
|
|
85
86
|
this.filePath = path.resolve(filePath);
|
|
86
87
|
this.lockPath = `${this.filePath}.lock`;
|
|
87
88
|
}
|
|
88
89
|
/**
|
|
89
90
|
* Create a SessionDiskStore for a specific backend URL.
|
|
90
|
-
* Sessions are stored in
|
|
91
|
+
* Sessions are stored in $CONDUCTOR_HOME/sessions/<host>.yaml.
|
|
92
|
+
* When CONDUCTOR_HOME is unset, it defaults to ~/.conductor.
|
|
91
93
|
*/
|
|
92
|
-
static forBackendUrl(backendUrl) {
|
|
94
|
+
static forBackendUrl(backendUrl, env = process.env) {
|
|
93
95
|
const host = extractHostKey(backendUrl);
|
|
94
|
-
const filePath = path.join(
|
|
96
|
+
const filePath = path.join(resolveConductorHome(env), 'sessions', `${host}.yaml`);
|
|
95
97
|
return new SessionDiskStore(filePath);
|
|
96
98
|
}
|
|
97
99
|
load() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@love-moon/conductor-sdk",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/lovemoon-ai/conductor.git"
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"typescript": "^5.6.3",
|
|
34
34
|
"vitest": "^2.1.4"
|
|
35
35
|
},
|
|
36
|
-
"gitCommitId": "
|
|
36
|
+
"gitCommitId": "26b2dbb"
|
|
37
37
|
}
|