@claude-flow/cli 3.71.1 → 3.71.3
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/.claude/helpers/auto-memory-hook.mjs +208 -54
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/settings.json +2 -1
- package/README.md +2 -0
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/daemon.js +20 -1
- package/dist/src/commands/embeddings.js +6 -6
- package/dist/src/commands/hive-mind-worker.d.ts +10 -0
- package/dist/src/commands/hive-mind-worker.js +20 -0
- package/dist/src/commands/hive-mind.js +27 -4
- package/dist/src/commands/init.js +4 -0
- package/dist/src/commands/session.js +10 -7
- package/dist/src/init/executor.js +15 -5
- package/dist/src/init/settings-generator.js +4 -0
- package/dist/src/init/types.d.ts +2 -0
- package/dist/src/init/types.js +4 -1
- package/dist/src/mcp-tools/agentdb-tools.js +3 -3
- package/dist/src/mcp-tools/claims-tools.js +23 -10
- package/dist/src/mcp-tools/daa-tools.js +22 -2
- package/dist/src/mcp-tools/embeddings-tools.js +2 -1
- package/dist/src/mcp-tools/hooks-tools.js +11 -1
- package/dist/src/mcp-tools/session-tools.js +42 -4
- package/dist/src/mcp-tools/task-tools.js +11 -0
- package/dist/src/mcp-tools/terminal-tools.js +5 -0
- package/dist/src/mcp-tools/workflow-tools.js +22 -3
- package/dist/src/memory/memory-bridge.js +29 -5
- package/dist/src/memory/memory-initializer.js +32 -10
- package/dist/src/missions/schemas.d.ts +10 -10
- package/dist/src/mods/headless-plugins.d.ts +16 -0
- package/dist/src/mods/headless-plugins.js +30 -1
- package/dist/src/services/claims-store.js +5 -2
- package/dist/src/services/daemon-autostart.d.ts +24 -0
- package/dist/src/services/daemon-autostart.js +55 -1
- package/dist/src/services/headless-worker-executor.d.ts +15 -0
- package/dist/src/services/headless-worker-executor.js +28 -0
- package/package.json +1 -1
- package/plugins/ruflo-console/dist/tui.mjs +134 -134
|
@@ -3115,16 +3115,38 @@ export async function listEntries(options) {
|
|
|
3115
3115
|
const total = countRows[0]?.[0] || 0;
|
|
3116
3116
|
// Get entries. ADR-472: embedding rows are capped at MAX_LIST_EMBEDDINGS.
|
|
3117
3117
|
const listLimit = options.includeEmbedding ? Math.min(safeLimit, MAX_LIST_EMBEDDINGS) : safeLimit;
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3118
|
+
const columns = 'id, key, namespace, content, embedding, access_count, created_at, updated_at, provenance_type, expires_at, tags';
|
|
3119
|
+
// #4686: page row identities first so the temporary sort B-tree does not
|
|
3120
|
+
// carry full content/embedding payloads for rows outside the page.
|
|
3121
|
+
const pagedSql = `SELECT ${columns} FROM memory_entries
|
|
3122
|
+
WHERE _rowid_ IN (
|
|
3123
|
+
SELECT _rowid_ FROM memory_entries WHERE ${whereSql}
|
|
3124
|
+
ORDER BY updated_at DESC LIMIT ? OFFSET ?
|
|
3125
|
+
)
|
|
3126
|
+
ORDER BY updated_at DESC`;
|
|
3127
|
+
const originalSql = `SELECT ${columns} FROM memory_entries WHERE ${whereSql}
|
|
3128
|
+
ORDER BY updated_at DESC LIMIT ? OFFSET ?`;
|
|
3129
|
+
let listRows = [];
|
|
3130
|
+
const runList = (sql) => {
|
|
3131
|
+
const stmt = db.prepare(sql);
|
|
3132
|
+
try {
|
|
3133
|
+
stmt.bind([...whereParams, listLimit, safeOffset]);
|
|
3134
|
+
const rows = [];
|
|
3135
|
+
while (stmt.step())
|
|
3136
|
+
rows.push(stmt.get());
|
|
3137
|
+
return rows;
|
|
3138
|
+
}
|
|
3139
|
+
finally {
|
|
3140
|
+
stmt.free();
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
try {
|
|
3144
|
+
listRows = runList(pagedSql);
|
|
3145
|
+
}
|
|
3146
|
+
catch {
|
|
3147
|
+
// Legacy WITHOUT ROWID tables have no _rowid_: keep the original query.
|
|
3148
|
+
listRows = runList(originalSql);
|
|
3149
|
+
}
|
|
3128
3150
|
const entries = [];
|
|
3129
3151
|
for (const row of listRows) {
|
|
3130
3152
|
const [id, key, ns, content, embedding, accessCount, createdAt, updatedAt, provenanceTypeVal, expiresAtVal, tagsJson] = row;
|
|
@@ -77,15 +77,15 @@ export declare const missionRecordSchema: z.ZodObject<{
|
|
|
77
77
|
updatedAt: string;
|
|
78
78
|
schemaVersion: 1;
|
|
79
79
|
state: "completed" | "cancelled" | "failed" | "blocked" | "running" | "paused" | "queued" | "draft" | "planned" | "awaitingAuthorization" | "pauseRequested" | "verifying" | "cancelRequested";
|
|
80
|
-
acceptance: {
|
|
81
|
-
revision: number;
|
|
82
|
-
criteriaDigest: string;
|
|
83
|
-
};
|
|
84
80
|
plan: {
|
|
85
81
|
digest: string;
|
|
86
82
|
revision: number;
|
|
87
83
|
taskGraphRef: string;
|
|
88
84
|
};
|
|
85
|
+
acceptance: {
|
|
86
|
+
revision: number;
|
|
87
|
+
criteriaDigest: string;
|
|
88
|
+
};
|
|
89
89
|
objective: string;
|
|
90
90
|
missionId: string;
|
|
91
91
|
tenantId: string;
|
|
@@ -103,15 +103,15 @@ export declare const missionRecordSchema: z.ZodObject<{
|
|
|
103
103
|
updatedAt: string;
|
|
104
104
|
schemaVersion: 1;
|
|
105
105
|
state: "completed" | "cancelled" | "failed" | "blocked" | "running" | "paused" | "queued" | "draft" | "planned" | "awaitingAuthorization" | "pauseRequested" | "verifying" | "cancelRequested";
|
|
106
|
-
acceptance: {
|
|
107
|
-
revision: number;
|
|
108
|
-
criteriaDigest: string;
|
|
109
|
-
};
|
|
110
106
|
plan: {
|
|
111
107
|
digest: string;
|
|
112
108
|
revision: number;
|
|
113
109
|
taskGraphRef: string;
|
|
114
110
|
};
|
|
111
|
+
acceptance: {
|
|
112
|
+
revision: number;
|
|
113
|
+
criteriaDigest: string;
|
|
114
|
+
};
|
|
115
115
|
objective: string;
|
|
116
116
|
missionId: string;
|
|
117
117
|
tenantId: string;
|
|
@@ -580,7 +580,6 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
580
580
|
} | undefined;
|
|
581
581
|
}>;
|
|
582
582
|
}, "strict", z.ZodTypeAny, {
|
|
583
|
-
requestId: string;
|
|
584
583
|
plan: {
|
|
585
584
|
tasks: {
|
|
586
585
|
id: string;
|
|
@@ -617,10 +616,10 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
617
616
|
baseline?: string | undefined;
|
|
618
617
|
}[];
|
|
619
618
|
};
|
|
619
|
+
requestId: string;
|
|
620
620
|
missionId: string;
|
|
621
621
|
expectedRevision: number;
|
|
622
622
|
}, {
|
|
623
|
-
requestId: string;
|
|
624
623
|
plan: {
|
|
625
624
|
tasks: {
|
|
626
625
|
id: string;
|
|
@@ -657,6 +656,7 @@ export declare const planInputSchema: z.ZodObject<{
|
|
|
657
656
|
capabilities?: string[] | undefined;
|
|
658
657
|
} | undefined;
|
|
659
658
|
};
|
|
659
|
+
requestId: string;
|
|
660
660
|
missionId: string;
|
|
661
661
|
expectedRevision: number;
|
|
662
662
|
}>;
|
|
@@ -14,10 +14,20 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Profile override: `RUFLO_HEADLESS_PLUGINS` is a comma list of plugin names;
|
|
16
16
|
* `none` (or an empty value) turns plugin loading off.
|
|
17
|
+
*
|
|
18
|
+
* Permission mode: Claude Code 2.1.302 starts a `claude -p` with no
|
|
19
|
+
* permission mode in `manual` mode on third-party providers, with telemetry
|
|
20
|
+
* off, or on a fresh install. A headless worker has nobody to answer a prompt,
|
|
21
|
+
* so a print-mode argv without its own `--permission-mode` or
|
|
22
|
+
* `--dangerously-skip-permissions` gets `--permission-mode auto` — the mode
|
|
23
|
+
* first-party installs already started in. `RUFLO_HEADLESS_PERMISSION_MODE`
|
|
24
|
+
* picks another mode; `none` (or an empty value) passes no flag.
|
|
17
25
|
*/
|
|
18
26
|
import { type ResolveOptions, type PluginPaths } from './plugin-paths.js';
|
|
19
27
|
export declare const DEFAULT_WORKER_PLUGINS: readonly string[];
|
|
20
28
|
export declare const PROFILE_ENV = "RUFLO_HEADLESS_PLUGINS";
|
|
29
|
+
export declare const PERMISSION_MODE_ENV = "RUFLO_HEADLESS_PERMISSION_MODE";
|
|
30
|
+
export declare const DEFAULT_HEADLESS_PERMISSION_MODE = "auto";
|
|
21
31
|
export interface HeadlessPluginOptions extends ResolveOptions {
|
|
22
32
|
/** Plugin names to load; defaults to the env profile, else DEFAULT_WORKER_PLUGINS. */
|
|
23
33
|
profile?: readonly string[];
|
|
@@ -40,5 +50,11 @@ export declare function resetHeadlessPluginWarnings(): void;
|
|
|
40
50
|
export declare function workerProfile(env?: NodeJS.ProcessEnv, explicit?: readonly string[]): string[];
|
|
41
51
|
/** Every `--plugin-dir` value already in argv (both `--plugin-dir x` and `--plugin-dir=x`). */
|
|
42
52
|
export declare function existingPluginDirs(argv: readonly string[]): string[];
|
|
53
|
+
/**
|
|
54
|
+
* The `--permission-mode` args to append to a headless argv: none when the
|
|
55
|
+
* argv is not print mode or already sets a mode, else the env choice
|
|
56
|
+
* (default `auto`). An unknown mode is ignored with the default used instead.
|
|
57
|
+
*/
|
|
58
|
+
export declare function headlessPermissionArgs(argv: readonly string[], env?: NodeJS.ProcessEnv): string[];
|
|
43
59
|
export declare function withHeadlessPlugins<E extends Record<string, string | undefined>>(argv: readonly string[], env: E, opts?: HeadlessPluginOptions): HeadlessPluginResult<E>;
|
|
44
60
|
//# sourceMappingURL=headless-plugins.d.ts.map
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Profile override: `RUFLO_HEADLESS_PLUGINS` is a comma list of plugin names;
|
|
16
16
|
* `none` (or an empty value) turns plugin loading off.
|
|
17
|
+
*
|
|
18
|
+
* Permission mode: Claude Code 2.1.302 starts a `claude -p` with no
|
|
19
|
+
* permission mode in `manual` mode on third-party providers, with telemetry
|
|
20
|
+
* off, or on a fresh install. A headless worker has nobody to answer a prompt,
|
|
21
|
+
* so a print-mode argv without its own `--permission-mode` or
|
|
22
|
+
* `--dangerously-skip-permissions` gets `--permission-mode auto` — the mode
|
|
23
|
+
* first-party installs already started in. `RUFLO_HEADLESS_PERMISSION_MODE`
|
|
24
|
+
* picks another mode; `none` (or an empty value) passes no flag.
|
|
17
25
|
*/
|
|
18
26
|
import { realpathSync } from 'node:fs';
|
|
19
27
|
import { basename, resolve } from 'node:path';
|
|
@@ -21,6 +29,9 @@ import { ENABLE_ENV } from './install.js';
|
|
|
21
29
|
import { resolvePluginPaths } from './plugin-paths.js';
|
|
22
30
|
export const DEFAULT_WORKER_PLUGINS = ['ruflo-mods'];
|
|
23
31
|
export const PROFILE_ENV = 'RUFLO_HEADLESS_PLUGINS';
|
|
32
|
+
export const PERMISSION_MODE_ENV = 'RUFLO_HEADLESS_PERMISSION_MODE';
|
|
33
|
+
export const DEFAULT_HEADLESS_PERMISSION_MODE = 'auto';
|
|
34
|
+
const PERMISSION_MODES = new Set(['acceptEdits', 'auto', 'bypassPermissions', 'manual', 'default', 'dontAsk', 'plan']);
|
|
24
35
|
const warned = new Set();
|
|
25
36
|
/** For tests: forget which missing plugins were already logged. */
|
|
26
37
|
export function resetHeadlessPluginWarnings() {
|
|
@@ -56,9 +67,27 @@ export function existingPluginDirs(argv) {
|
|
|
56
67
|
}
|
|
57
68
|
return dirs;
|
|
58
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* The `--permission-mode` args to append to a headless argv: none when the
|
|
72
|
+
* argv is not print mode or already sets a mode, else the env choice
|
|
73
|
+
* (default `auto`). An unknown mode is ignored with the default used instead.
|
|
74
|
+
*/
|
|
75
|
+
export function headlessPermissionArgs(argv, env = process.env) {
|
|
76
|
+
const isPrint = argv.some((a) => a === '-p' || a === '--print');
|
|
77
|
+
if (!isPrint)
|
|
78
|
+
return [];
|
|
79
|
+
const hasMode = argv.some((a) => a === '--permission-mode' || a.startsWith('--permission-mode=') || a === '--dangerously-skip-permissions');
|
|
80
|
+
if (hasMode)
|
|
81
|
+
return [];
|
|
82
|
+
const raw = env[PERMISSION_MODE_ENV];
|
|
83
|
+
if (raw !== undefined && (raw.trim() === '' || raw.trim().toLowerCase() === 'none'))
|
|
84
|
+
return [];
|
|
85
|
+
const mode = raw !== undefined && PERMISSION_MODES.has(raw.trim()) ? raw.trim() : DEFAULT_HEADLESS_PERMISSION_MODE;
|
|
86
|
+
return ['--permission-mode', mode];
|
|
87
|
+
}
|
|
59
88
|
export function withHeadlessPlugins(argv, env, opts = {}) {
|
|
60
89
|
const profile = workerProfile(opts.env ?? env, opts.profile);
|
|
61
|
-
const outArgv = [...argv];
|
|
90
|
+
const outArgv = [...argv, ...headlessPermissionArgs(argv, opts.env ?? env)];
|
|
62
91
|
const outEnv = { ...env };
|
|
63
92
|
if (profile.length === 0)
|
|
64
93
|
return { argv: outArgv, env: outEnv, added: [], missing: [] };
|
|
@@ -11,7 +11,7 @@ export function readClaimsStore(file) {
|
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
13
13
|
if (error.code === 'ENOENT')
|
|
14
|
-
return { claims:
|
|
14
|
+
return { claims: Object.create(null), stealable: Object.create(null), contests: Object.create(null) };
|
|
15
15
|
throw error;
|
|
16
16
|
}
|
|
17
17
|
const data = JSON.parse(text);
|
|
@@ -35,6 +35,9 @@ export function readClaimsStore(file) {
|
|
|
35
35
|
if (data[key] !== undefined && !record(data[key]))
|
|
36
36
|
throw new Error(`Invalid claims store: ${key} must be an object`);
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
// SECURITY: issue ids come from tool input; null-prototype maps keep a lookup
|
|
39
|
+
// by `constructor`/`__proto__` from resolving to an inherited built-in.
|
|
40
|
+
const nullProto = (map) => Object.assign(Object.create(null), map ?? {});
|
|
41
|
+
return { ...data, claims, stealable: nullProto(data.stealable), contests: nullProto(data.contests) };
|
|
39
42
|
}
|
|
40
43
|
//# sourceMappingURL=claims-store.js.map
|
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
/** True if a live daemon already holds the project's pidfile. */
|
|
2
2
|
export declare function isDaemonAlive(projectRoot: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* #4654: an explicit `daemon stop` is an opt-out that must survive the next
|
|
5
|
+
* hook. Every ruflo-core plugin hook (PreToolUse/PostToolUse, PreCompact, Stop)
|
|
6
|
+
* runs the CLI, and the CLI auto-starts a daemon whenever none is alive, so a
|
|
7
|
+
* stopped daemon came back within minutes. `daemon stop` now writes this
|
|
8
|
+
* marker, auto-start honours it, and only an explicit `daemon start` clears it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DAEMON_STOP_MARKER = "daemon.stopped";
|
|
11
|
+
/**
|
|
12
|
+
* Set on the daemon that auto-start spawns, so that spawned `daemon start` does
|
|
13
|
+
* not clear a stop marker written after auto-start checked it (a `daemon stop`
|
|
14
|
+
* racing a hook). Only a user-driven start clears the marker.
|
|
15
|
+
*/
|
|
16
|
+
export declare const AUTOSTART_ENV = "RUFLO_DAEMON_AUTOSTARTED";
|
|
17
|
+
/** True if the user explicitly stopped the daemon for this project (#4654). */
|
|
18
|
+
export declare function isDaemonStopMarked(projectRoot: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Record an explicit `daemon stop` (#4654). Writes only into a directory that is
|
|
21
|
+
* already a Ruflo project or already has `.claude-flow/`, so `daemon stop` in an
|
|
22
|
+
* unrelated directory does not litter it. Best-effort; never throws.
|
|
23
|
+
*/
|
|
24
|
+
export declare function writeDaemonStopMarker(projectRoot: string): boolean;
|
|
25
|
+
/** Clear the stop marker. Called by an explicit `daemon start` (#4654). Never throws. */
|
|
26
|
+
export declare function clearDaemonStopMarker(projectRoot: string): void;
|
|
3
27
|
/**
|
|
4
28
|
* Return true only when the directory contains a durable Ruflo project marker.
|
|
5
29
|
*
|
|
@@ -81,6 +81,57 @@ function autostartDisabledByProjectConfig(projectRoot) {
|
|
|
81
81
|
}
|
|
82
82
|
return false;
|
|
83
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* #4654: an explicit `daemon stop` is an opt-out that must survive the next
|
|
86
|
+
* hook. Every ruflo-core plugin hook (PreToolUse/PostToolUse, PreCompact, Stop)
|
|
87
|
+
* runs the CLI, and the CLI auto-starts a daemon whenever none is alive, so a
|
|
88
|
+
* stopped daemon came back within minutes. `daemon stop` now writes this
|
|
89
|
+
* marker, auto-start honours it, and only an explicit `daemon start` clears it.
|
|
90
|
+
*/
|
|
91
|
+
export const DAEMON_STOP_MARKER = 'daemon.stopped';
|
|
92
|
+
/**
|
|
93
|
+
* Set on the daemon that auto-start spawns, so that spawned `daemon start` does
|
|
94
|
+
* not clear a stop marker written after auto-start checked it (a `daemon stop`
|
|
95
|
+
* racing a hook). Only a user-driven start clears the marker.
|
|
96
|
+
*/
|
|
97
|
+
export const AUTOSTART_ENV = 'RUFLO_DAEMON_AUTOSTARTED';
|
|
98
|
+
function stopMarkerPath(projectRoot) {
|
|
99
|
+
return path.join(projectRoot, '.claude-flow', DAEMON_STOP_MARKER);
|
|
100
|
+
}
|
|
101
|
+
/** True if the user explicitly stopped the daemon for this project (#4654). */
|
|
102
|
+
export function isDaemonStopMarked(projectRoot) {
|
|
103
|
+
try {
|
|
104
|
+
return fs.existsSync(stopMarkerPath(projectRoot));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Record an explicit `daemon stop` (#4654). Writes only into a directory that is
|
|
112
|
+
* already a Ruflo project or already has `.claude-flow/`, so `daemon stop` in an
|
|
113
|
+
* unrelated directory does not litter it. Best-effort; never throws.
|
|
114
|
+
*/
|
|
115
|
+
export function writeDaemonStopMarker(projectRoot) {
|
|
116
|
+
try {
|
|
117
|
+
const dir = path.join(projectRoot, '.claude-flow');
|
|
118
|
+
if (!fs.existsSync(dir) && !isRufloProject(projectRoot))
|
|
119
|
+
return false;
|
|
120
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
121
|
+
fs.writeFileSync(stopMarkerPath(projectRoot), JSON.stringify({ stoppedAt: new Date().toISOString(), pid: process.pid }) + '\n');
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Clear the stop marker. Called by an explicit `daemon start` (#4654). Never throws. */
|
|
129
|
+
export function clearDaemonStopMarker(projectRoot) {
|
|
130
|
+
try {
|
|
131
|
+
fs.unlinkSync(stopMarkerPath(projectRoot));
|
|
132
|
+
}
|
|
133
|
+
catch { /* absent */ }
|
|
134
|
+
}
|
|
84
135
|
function autostartDisabled(projectRoot) {
|
|
85
136
|
if (/^(0|false|no|off)$/i.test(process.env.RUFLO_DAEMON_AUTOSTART ?? ''))
|
|
86
137
|
return true;
|
|
@@ -166,7 +217,7 @@ const defaultSpawn = (projectRoot) => {
|
|
|
166
217
|
cwd: projectRoot,
|
|
167
218
|
detached: true,
|
|
168
219
|
stdio: 'ignore',
|
|
169
|
-
env: { ...process.env },
|
|
220
|
+
env: { ...process.env, [AUTOSTART_ENV]: '1' },
|
|
170
221
|
});
|
|
171
222
|
child.unref();
|
|
172
223
|
};
|
|
@@ -186,6 +237,9 @@ export function ensureDaemonRunning(startDir, opts = {}) {
|
|
|
186
237
|
if (!isRufloProject(projectRoot)) {
|
|
187
238
|
return { started: false, reason: 'not a ruflo project' };
|
|
188
239
|
}
|
|
240
|
+
if (isDaemonStopMarked(projectRoot)) {
|
|
241
|
+
return { started: false, reason: 'stopped by user (daemon stop); run `daemon start` to re-enable' };
|
|
242
|
+
}
|
|
189
243
|
const alive = (opts.isAlive ?? isDaemonAlive)(projectRoot);
|
|
190
244
|
if (alive)
|
|
191
245
|
return { started: false, reason: 'already running' };
|
|
@@ -123,6 +123,12 @@ export interface HeadlessExecutionResult {
|
|
|
123
123
|
inputTokens?: number;
|
|
124
124
|
outputTokens?: number;
|
|
125
125
|
costUsd?: number;
|
|
126
|
+
/**
|
|
127
|
+
* When the stored claude.ai sign-in the worker ran under ends (ISO 8601),
|
|
128
|
+
* from `credential_expires_at` in the `claude -p` JSON result (Claude Code
|
|
129
|
+
* 2.1.318+). Absent for API-key auth and older CLIs.
|
|
130
|
+
*/
|
|
131
|
+
credentialExpiresAt?: string;
|
|
126
132
|
/** Model used for execution */
|
|
127
133
|
model: string;
|
|
128
134
|
/** Sandbox mode used */
|
|
@@ -202,7 +208,16 @@ export interface ClaudePrintEnvelope {
|
|
|
202
208
|
outputTokens?: number;
|
|
203
209
|
costUsd?: number;
|
|
204
210
|
durationMs?: number;
|
|
211
|
+
/** ISO 8601 end of the stored claude.ai sign-in (Claude Code 2.1.318+). */
|
|
212
|
+
credentialExpiresAt?: string;
|
|
205
213
|
}
|
|
214
|
+
/** Days before a sign-in ends at which the executor emits 'credential-expiring' (matches Claude Code's own stderr warning). */
|
|
215
|
+
export declare const CREDENTIAL_EXPIRY_WARN_DAYS = 10;
|
|
216
|
+
/**
|
|
217
|
+
* Normalise `credential_expires_at` to ISO 8601. Accepts an ISO string or an
|
|
218
|
+
* epoch number (seconds or milliseconds); anything else is undefined.
|
|
219
|
+
*/
|
|
220
|
+
export declare function normalizeCredentialExpiry(v: unknown): string | undefined;
|
|
206
221
|
/**
|
|
207
222
|
* #2661 root-fix — best-effort parse of `claude --print --output-format
|
|
208
223
|
* json`'s response envelope. Deliberately lenient: probes a couple of
|
|
@@ -337,6 +337,23 @@ export function isLocalWorker(type) {
|
|
|
337
337
|
export function getModelId(model) {
|
|
338
338
|
return MODEL_IDS[model];
|
|
339
339
|
}
|
|
340
|
+
/** Days before a sign-in ends at which the executor emits 'credential-expiring' (matches Claude Code's own stderr warning). */
|
|
341
|
+
export const CREDENTIAL_EXPIRY_WARN_DAYS = 10;
|
|
342
|
+
/**
|
|
343
|
+
* Normalise `credential_expires_at` to ISO 8601. Accepts an ISO string or an
|
|
344
|
+
* epoch number (seconds or milliseconds); anything else is undefined.
|
|
345
|
+
*/
|
|
346
|
+
export function normalizeCredentialExpiry(v) {
|
|
347
|
+
let ms;
|
|
348
|
+
if (typeof v === 'number' && Number.isFinite(v) && v > 0)
|
|
349
|
+
ms = v < 1e12 ? v * 1000 : v;
|
|
350
|
+
else if (typeof v === 'string' && v.trim()) {
|
|
351
|
+
const t = Date.parse(v);
|
|
352
|
+
if (Number.isFinite(t))
|
|
353
|
+
ms = t;
|
|
354
|
+
}
|
|
355
|
+
return ms === undefined ? undefined : new Date(ms).toISOString();
|
|
356
|
+
}
|
|
340
357
|
/**
|
|
341
358
|
* #2661 root-fix — best-effort parse of `claude --print --output-format
|
|
342
359
|
* json`'s response envelope. Deliberately lenient: probes a couple of
|
|
@@ -371,6 +388,7 @@ export function parseClaudePrintJsonEnvelope(raw) {
|
|
|
371
388
|
outputTokens: numOrUndef(usage?.output_tokens ?? usage?.outputTokens),
|
|
372
389
|
costUsd: numOrUndef(obj.total_cost_usd ?? obj.cost_usd ?? obj.totalCostUsd),
|
|
373
390
|
durationMs: numOrUndef(obj.duration_ms ?? obj.durationMs),
|
|
391
|
+
credentialExpiresAt: normalizeCredentialExpiry(obj.credential_expires_at ?? obj.credentialExpiresAt),
|
|
374
392
|
};
|
|
375
393
|
}
|
|
376
394
|
/**
|
|
@@ -779,7 +797,16 @@ export class HeadlessWorkerExecutor extends EventEmitter {
|
|
|
779
797
|
executionId,
|
|
780
798
|
error: result.error,
|
|
781
799
|
...(result.missingPlugins ? { missingPlugins: result.missingPlugins } : {}),
|
|
800
|
+
...(result.credentialExpiresAt ? { credentialExpiresAt: result.credentialExpiresAt } : {}),
|
|
782
801
|
};
|
|
802
|
+
// Claude Code 2.1.318: surface a claude.ai sign-in that ends soon, so the
|
|
803
|
+
// daemon can say so before workers start failing on auth.
|
|
804
|
+
if (result.credentialExpiresAt) {
|
|
805
|
+
const daysLeft = (Date.parse(result.credentialExpiresAt) - Date.now()) / 86_400_000;
|
|
806
|
+
if (daysLeft <= CREDENTIAL_EXPIRY_WARN_DAYS) {
|
|
807
|
+
this.emit('credential-expiring', { expiresAt: result.credentialExpiresAt, daysLeft: Math.max(0, Math.floor(daysLeft)), workerType });
|
|
808
|
+
}
|
|
809
|
+
}
|
|
783
810
|
// Log result
|
|
784
811
|
this.logExecution(executionId, 'result', JSON.stringify(executionResult, null, 2));
|
|
785
812
|
// #2661 root-fix — structured per-launch telemetry, receipted
|
|
@@ -1182,6 +1209,7 @@ Analyze the above codebase context and provide your response following the forma
|
|
|
1182
1209
|
: undefined,
|
|
1183
1210
|
costUsd: envelope?.costUsd,
|
|
1184
1211
|
apiDurationMs: envelope?.durationMs,
|
|
1212
|
+
credentialExpiresAt: envelope?.credentialExpiresAt,
|
|
1185
1213
|
error: code !== 0 ? stderr || `Process exited with code ${code}` : undefined,
|
|
1186
1214
|
});
|
|
1187
1215
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.71.
|
|
3
|
+
"version": "3.71.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|