@claude-flow/cli 3.71.2 → 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/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/init/settings-generator.js +4 -0
- package/dist/src/mcp-tools/agentdb-tools.js +3 -3
- package/dist/src/mcp-tools/claims-tools.js +16 -9
- package/dist/src/mcp-tools/hooks-tools.js +11 -1
- package/dist/src/mcp-tools/task-tools.js +11 -0
- 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/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 +132 -132
|
@@ -985,12 +985,12 @@ export const agentdbBatch = {
|
|
|
985
985
|
// ===== agentdb_context_synthesize — Synthesize context from memories =====
|
|
986
986
|
export const agentdbContextSynthesize = {
|
|
987
987
|
name: 'agentdb_context-synthesize',
|
|
988
|
-
description: '
|
|
988
|
+
description: 'Summarize validated task episodes (task, reward, success, critique) that match a query: observed success rate, reward and critique-derived patterns. Ordinary documents, research notes and graph entities are not episodes, so for them it answers no-memories or no-eligible-episodes; read those with memory_search or memory_retrieve instead. Use when generic memory_* tools are wrong because you need AgentDB-specific controllers (HNSW vector search, hierarchical tiers, causal-graph links, pattern store/recall, RaBitQ quantization). For simple key-value persistence, memory_store/memory_retrieve are simpler. For unrelated file work, native Read/Write are fine.',
|
|
989
989
|
inputSchema: {
|
|
990
990
|
type: 'object',
|
|
991
991
|
properties: {
|
|
992
|
-
query: { type: 'string', description: '
|
|
993
|
-
maxEntries: { type: 'number', description: 'Maximum
|
|
992
|
+
query: { type: 'string', description: 'Task to find matching validated episodes for' },
|
|
993
|
+
maxEntries: { type: 'number', description: 'Maximum episodes to include (default: 10)' },
|
|
994
994
|
},
|
|
995
995
|
required: ['query'],
|
|
996
996
|
},
|
|
@@ -36,6 +36,10 @@ function formatClaimant(claimant) {
|
|
|
36
36
|
? `human:${claimant.userId}:${claimant.name}`
|
|
37
37
|
: `agent:${claimant.agentId}:${claimant.agentType}`;
|
|
38
38
|
}
|
|
39
|
+
/** An agent's claim that is still work in hand (not completed). */
|
|
40
|
+
function isOpenAgentClaim(claim) {
|
|
41
|
+
return claim.claimant.type === 'agent' && claim.status !== 'completed';
|
|
42
|
+
}
|
|
39
43
|
function parseClaimant(str) {
|
|
40
44
|
const parts = str.split(':');
|
|
41
45
|
if (parts[0] === 'human' && parts.length >= 3) {
|
|
@@ -274,6 +278,10 @@ export const claimsTools = [
|
|
|
274
278
|
if (progress !== undefined) {
|
|
275
279
|
claim.progress = Math.min(100, Math.max(0, progress));
|
|
276
280
|
}
|
|
281
|
+
// A claim being handed to a named claimant is no longer up for grabs:
|
|
282
|
+
// drop it from the list claims_stealable and claims_steal read, so it
|
|
283
|
+
// cannot be stolen while the handoff is pending or after it is accepted.
|
|
284
|
+
delete store.stealable[issueId];
|
|
277
285
|
store.claims[issueId] = claim;
|
|
278
286
|
saveClaims(store);
|
|
279
287
|
return {
|
|
@@ -678,12 +686,12 @@ export const claimsTools = [
|
|
|
678
686
|
return { success: false, error: v.error };
|
|
679
687
|
}
|
|
680
688
|
const store = loadClaims();
|
|
681
|
-
|
|
689
|
+
// A completed claim stays in the store but is no longer work in hand,
|
|
690
|
+
// so it does not count toward an agent's load.
|
|
691
|
+
const openAgentClaims = Object.values(store.claims).filter(isOpenAgentClaim);
|
|
682
692
|
// Group claims by agent
|
|
683
693
|
const agentLoads = new Map();
|
|
684
|
-
for (const claim of
|
|
685
|
-
if (claim.claimant.type !== 'agent')
|
|
686
|
-
continue;
|
|
694
|
+
for (const claim of openAgentClaims) {
|
|
687
695
|
const key = claim.claimant.agentId;
|
|
688
696
|
if (!agentLoads.has(key)) {
|
|
689
697
|
agentLoads.set(key, {
|
|
@@ -723,7 +731,7 @@ export const claimsTools = [
|
|
|
723
731
|
success: true,
|
|
724
732
|
loads: result,
|
|
725
733
|
totalAgents: result.length,
|
|
726
|
-
totalClaims:
|
|
734
|
+
totalClaims: openAgentClaims.length,
|
|
727
735
|
avgUtilization: result.length > 0
|
|
728
736
|
? result.reduce((sum, l) => sum + l.utilization, 0) / result.length
|
|
729
737
|
: 0,
|
|
@@ -802,12 +810,11 @@ export const claimsTools = [
|
|
|
802
810
|
const dryRun = input.dryRun !== false;
|
|
803
811
|
const targetUtilization = input.targetUtilization || 0.7;
|
|
804
812
|
const store = loadClaims();
|
|
805
|
-
|
|
813
|
+
// Completed claims are not load, as in claims_load.
|
|
814
|
+
const openAgentClaims = Object.values(store.claims).filter(isOpenAgentClaim);
|
|
806
815
|
// Group by agent
|
|
807
816
|
const agentLoads = new Map();
|
|
808
|
-
for (const claim of
|
|
809
|
-
if (claim.claimant.type !== 'agent')
|
|
810
|
-
continue;
|
|
817
|
+
for (const claim of openAgentClaims) {
|
|
811
818
|
const key = claim.claimant.agentId;
|
|
812
819
|
if (!agentLoads.has(key)) {
|
|
813
820
|
agentLoads.set(key, { agentId: key, agentType: claim.claimant.agentType, claims: [] });
|
|
@@ -4096,11 +4096,21 @@ export const hooksIntelligenceLearn = {
|
|
|
4096
4096
|
inputSchema: {
|
|
4097
4097
|
type: 'object',
|
|
4098
4098
|
properties: {
|
|
4099
|
-
trajectoryIds: { type: 'array', items: { type: 'string' }, description: '
|
|
4099
|
+
trajectoryIds: { type: 'array', items: { type: 'string' }, description: 'Not supported: a learning cycle always covers every recorded trajectory. Supplying this returns an error and runs nothing, rather than learning from more than was asked (#4693).' },
|
|
4100
4100
|
consolidate: { type: 'boolean', description: 'Run EWC++ consolidation' },
|
|
4101
4101
|
},
|
|
4102
4102
|
},
|
|
4103
4103
|
handler: async (params) => {
|
|
4104
|
+
// #4693: distillLearning() and the background pass have no way to select
|
|
4105
|
+
// trajectories, so a request scoped to some must not trigger learning over all.
|
|
4106
|
+
// Refuse it before anything is imported or mutated; omitting the field is unchanged.
|
|
4107
|
+
if (params.trajectoryIds !== undefined) {
|
|
4108
|
+
return {
|
|
4109
|
+
success: false,
|
|
4110
|
+
learned: false,
|
|
4111
|
+
error: 'hooks_intelligence_learn: trajectoryIds is not supported; a learning cycle always covers every recorded trajectory. Omit trajectoryIds to run it.',
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
4104
4114
|
const consolidate = params.consolidate !== false;
|
|
4105
4115
|
const startTime = Date.now();
|
|
4106
4116
|
// AUDIT FIX #5: actually TRIGGER a learning/consolidation cycle instead of
|
|
@@ -117,6 +117,10 @@ export const taskTools = [
|
|
|
117
117
|
const vDesc = validateText(input.description, 'description');
|
|
118
118
|
if (!vDesc.valid)
|
|
119
119
|
return { success: false, error: vDesc.error };
|
|
120
|
+
if (input.assignTo !== undefined && (!Array.isArray(input.assignTo)
|
|
121
|
+
|| !input.assignTo.every(id => validateIdentifier(id, 'assignTo').valid))) {
|
|
122
|
+
return { success: false, error: 'assignTo must be an array of agent ids' };
|
|
123
|
+
}
|
|
120
124
|
const store = loadTaskStore();
|
|
121
125
|
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
122
126
|
const task = {
|
|
@@ -134,6 +138,13 @@ export const taskTools = [
|
|
|
134
138
|
};
|
|
135
139
|
store.tasks[taskId] = task;
|
|
136
140
|
saveTaskStore(store);
|
|
141
|
+
// Assigning at creation must mark the workers busy, as task_assign and task_update do;
|
|
142
|
+
// otherwise they read idle while holding the task, and task_complete or task_cancel,
|
|
143
|
+
// which only release a worker whose currentTask is this task, never free them either.
|
|
144
|
+
updateAgents(task.assignedTo, (agent) => {
|
|
145
|
+
agent.status = 'busy';
|
|
146
|
+
agent.currentTask = taskId;
|
|
147
|
+
});
|
|
137
148
|
return {
|
|
138
149
|
taskId,
|
|
139
150
|
type: task.type,
|
|
@@ -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: [] };
|
|
@@ -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",
|