@adhdev/daemon-core 1.0.18-rc.15 → 1.0.18-rc.17
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/dist/commands/cli-manager.d.ts +9 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +372 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +370 -34
- package/dist/index.mjs.map +1 -1
- package/dist/providers/auto-approve-modes.d.ts +14 -0
- package/dist/providers/chat-message-normalization.d.ts +22 -0
- package/dist/providers/cli-provider-instance.d.ts +4 -0
- package/dist/providers/contracts.d.ts +17 -0
- package/dist/providers/provider-schema.d.ts +1 -0
- package/dist/repo-mesh-types.d.ts +16 -6
- package/dist/shared-types.d.ts +3 -1
- package/package.json +3 -3
- package/src/commands/cli-manager.ts +54 -8
- package/src/index.ts +3 -1
- package/src/mesh/mesh-completion-synthesis.ts +12 -1
- package/src/mesh/mesh-event-forwarding.ts +6 -2
- package/src/mesh/mesh-queue-assignment.ts +13 -6
- package/src/mesh/mesh-reconcile-loop.ts +124 -4
- package/src/providers/auto-approve-modes.ts +97 -0
- package/src/providers/chat-message-normalization.ts +53 -0
- package/src/providers/cli-provider-instance.ts +45 -13
- package/src/providers/contracts.ts +25 -1
- package/src/providers/provider-schema.ts +83 -0
- package/src/providers/sdk/v1/schemas/cli/provider.schema.json +44 -0
- package/src/repo-mesh-types.ts +65 -12
- package/src/shared-types.ts +3 -1
- package/src/status/snapshot.ts +2 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AutoApproveMode, AutoApproveModeRisk, AutoApproveModeStrategy, ProviderModule } from './contracts.js';
|
|
2
|
+
export interface ResolvedAutoApproveMode {
|
|
3
|
+
active: boolean;
|
|
4
|
+
strategy: AutoApproveModeStrategy;
|
|
5
|
+
modeId: string;
|
|
6
|
+
}
|
|
7
|
+
/** Runtime defense-in-depth for provider definitions that bypass schema validation. */
|
|
8
|
+
export declare function deriveAutoApproveModeRisk(mode: Pick<AutoApproveMode, 'risk' | 'launchArgs'>): AutoApproveModeRisk;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve new mode settings before the legacy boolean. A stale/unknown explicit
|
|
11
|
+
* mode id fails closed instead of falling through to an enabled legacy setting.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveProviderAutoApproveMode(provider: ProviderModule, settings: Record<string, unknown> | undefined): ResolvedAutoApproveMode;
|
|
14
|
+
export declare function findProviderAutoApproveMode(provider: ProviderModule | undefined, modeId: string): AutoApproveMode | undefined;
|
|
@@ -58,6 +58,28 @@ export declare function extractFinalAssistantSummaryEvidence(messages: ChatMessa
|
|
|
58
58
|
* grace gate) on top of this structural check.
|
|
59
59
|
*/
|
|
60
60
|
export declare function selectFinalAssistantTurnEndMessage(messages: ChatMessage[] | null | undefined): ChatMessage | null;
|
|
61
|
+
/**
|
|
62
|
+
* EARLY-IDLE-COMPLETION-FALSE-POSITIVE — trailing tool/terminal activity detector.
|
|
63
|
+
*
|
|
64
|
+
* True when the transcript's LAST non-empty user-facing assistant bubble is followed
|
|
65
|
+
* by one or more TOOL / TERMINAL activity bubbles (a tool_use/command the assistant
|
|
66
|
+
* fired AFTER its text) — i.e. the assistant emitted a preamble ("Let me explore…"),
|
|
67
|
+
* then started running Read/Grep, so the turn is still executing and its answer has
|
|
68
|
+
* not landed. Callers that would otherwise promote that preamble to a turn-end summary
|
|
69
|
+
* off a momentary (startup-grace / inter-tool) idle read use this as a veto.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately NARROW so the pure-PTY completion rescue is preserved:
|
|
72
|
+
* - Only TOOL/TERMINAL activity trailing the assistant vetoes. A trailing THOUGHT or
|
|
73
|
+
* status bubble does NOT (a finished turn can end on an internal thought), matching
|
|
74
|
+
* selectFinalAssistantTurnEndMessage's skip set minus the "still-working" signals.
|
|
75
|
+
* - A genuinely FINISHED worker (final assistant last, no trailing tool activity —
|
|
76
|
+
* the kimi pure-PTY continuous-idle shape) returns false, so its early completion
|
|
77
|
+
* is untouched.
|
|
78
|
+
*
|
|
79
|
+
* Returns false when there is no final assistant bubble at all (the caller's
|
|
80
|
+
* selectFinalAssistantTurnEndMessage already handles that as "not a turn end").
|
|
81
|
+
*/
|
|
82
|
+
export declare function hasTrailingToolActivityAfterFinalAssistant(messages: ChatMessage[] | null | undefined): boolean;
|
|
61
83
|
export declare const BUILTIN_CHAT_MESSAGE_KINDS: readonly ["standard", "thought", "tool", "terminal", "system"];
|
|
62
84
|
export type BuiltinChatMessageKind = typeof BUILTIN_CHAT_MESSAGE_KINDS[number];
|
|
63
85
|
export type ChatMessageKind = BuiltinChatMessageKind | (string & {});
|
|
@@ -810,7 +810,11 @@ export declare class CliProviderInstance implements ProviderInstance {
|
|
|
810
810
|
getAdapter(): ProviderCliAdapter;
|
|
811
811
|
get cliType(): string;
|
|
812
812
|
get cliName(): string;
|
|
813
|
+
private resolveAutoApproveMode;
|
|
814
|
+
/** Legacy boolean view retained for internal/test compatibility. */
|
|
813
815
|
private shouldAutoApprove;
|
|
816
|
+
private shouldUsePtyAutoApprove;
|
|
817
|
+
private resetPtyAutoApproveState;
|
|
814
818
|
/** @see ProviderInstance.noteManualInteraction */
|
|
815
819
|
noteManualInteraction(now?: number, opts?: {
|
|
816
820
|
passive?: boolean;
|
|
@@ -389,6 +389,21 @@ export interface ProviderCompatibilityEntry {
|
|
|
389
389
|
ideVersion: string;
|
|
390
390
|
scriptDir: string;
|
|
391
391
|
}
|
|
392
|
+
export type AutoApproveModeStrategy = 'pty-parse-default' | 'launch-args' | 'post-boot-command';
|
|
393
|
+
export type AutoApproveModeRisk = 'safe' | 'caution' | 'dangerous';
|
|
394
|
+
export interface AutoApproveMode {
|
|
395
|
+
id: string;
|
|
396
|
+
label: string;
|
|
397
|
+
strategy: AutoApproveModeStrategy;
|
|
398
|
+
risk: AutoApproveModeRisk;
|
|
399
|
+
warning?: string;
|
|
400
|
+
launchArgs?: string[];
|
|
401
|
+
removeArgs?: string[];
|
|
402
|
+
}
|
|
403
|
+
export interface AutoApproveModesConfig {
|
|
404
|
+
default: string;
|
|
405
|
+
modes: AutoApproveMode[];
|
|
406
|
+
}
|
|
392
407
|
export interface ProviderModule {
|
|
393
408
|
/** Unique identifier (e.g. 'cline', 'cursor', 'gemini-cli') */
|
|
394
409
|
type: string;
|
|
@@ -418,6 +433,8 @@ export interface ProviderModule {
|
|
|
418
433
|
status?: string;
|
|
419
434
|
/** Inventory/support detail string maintained in adhdev-providers */
|
|
420
435
|
details?: string;
|
|
436
|
+
/** Provider-specific auto-approve choices and their launch/runtime strategy. */
|
|
437
|
+
autoApproveModes?: AutoApproveModesConfig;
|
|
421
438
|
/** Install instructions (shown when command is missing) */
|
|
422
439
|
install?: string;
|
|
423
440
|
/** Custom version detection command (e.g. 'cursor --version', 'claude -v') */
|
|
@@ -14,6 +14,7 @@ import type { GitRepoStatus, GitCompactSummary } from './git/git-types.js';
|
|
|
14
14
|
import type { MeshMissionSummary, MeshMissionSlimSummary } from './mesh/mesh-missions.js';
|
|
15
15
|
import type { MeshMagiActivitySummary } from './mesh/mesh-magi-status.js';
|
|
16
16
|
import type { MagiKindPanelMap, DifficultyBrainMap, NodeCapabilitySlot } from '@adhdev/mesh-shared';
|
|
17
|
+
import type { ProviderModule } from './providers/contracts.js';
|
|
17
18
|
export interface RepoMesh {
|
|
18
19
|
id: string;
|
|
19
20
|
name: string;
|
|
@@ -255,6 +256,8 @@ export interface RepoMeshPolicy {
|
|
|
255
256
|
* A node policy may override this per-node (RepoMeshNodePolicy.delegatedWorkerAutoApprove).
|
|
256
257
|
*/
|
|
257
258
|
delegatedWorkerAutoApprove?: boolean;
|
|
259
|
+
/** Explicit opt-in required before delegated workers may use a dangerous provider mode. */
|
|
260
|
+
delegatedWorkerDangerousModeAllow?: boolean;
|
|
258
261
|
/**
|
|
259
262
|
* MESH-SEND-KEYS (feature 3): opt-in to allow the coordinator to inject
|
|
260
263
|
* DESTRUCTIVE keys (CTRL_C / ESC) into a worker PTY via mesh_send_keys. These
|
|
@@ -406,6 +409,8 @@ export interface RepoMeshNodePolicy {
|
|
|
406
409
|
* precedence over the mesh-level policy for worker sessions launched onto this node.
|
|
407
410
|
*/
|
|
408
411
|
delegatedWorkerAutoApprove?: boolean;
|
|
412
|
+
/** Per-node override for dangerous delegated worker mode authorization. */
|
|
413
|
+
delegatedWorkerDangerousModeAllow?: boolean;
|
|
409
414
|
/**
|
|
410
415
|
* MESH-SEND-KEYS (feature 3): per-node override for
|
|
411
416
|
* RepoMeshPolicy.allowSendKeysDestructive.
|
|
@@ -524,13 +529,18 @@ export declare function normalizeAutoFastForwardPolicy(value: unknown): NonNulla
|
|
|
524
529
|
*/
|
|
525
530
|
export declare function mergeAndNormalizePolicy(base: RepoMeshPolicy | undefined, patch: Partial<RepoMeshPolicy> | undefined): RepoMeshPolicy;
|
|
526
531
|
/**
|
|
527
|
-
* Resolve
|
|
528
|
-
*
|
|
529
|
-
*
|
|
530
|
-
* `autoApprove`; it wins over the global per-provider-type autoApprove config because the
|
|
531
|
-
* launch path merges the envelope as a settingsOverride on top of the provider defaults.
|
|
532
|
+
* Resolve delegated worker auto-approve. Legacy providers return a boolean. Providers
|
|
533
|
+
* with modes return the default mode id, except a dangerous default is downgraded to a
|
|
534
|
+
* non-dangerous PTY mode unless mesh/node policy explicitly opts in.
|
|
532
535
|
*/
|
|
533
|
-
export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove'> | null): boolean;
|
|
536
|
+
export declare function resolveDelegatedWorkerAutoApprove(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, provider?: Pick<ProviderModule, 'autoApproveModes'> | null): boolean | string;
|
|
537
|
+
export declare function resolveDelegatedWorkerDangerousModeAllow(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerDangerousModeAllow'> | null): boolean;
|
|
538
|
+
/** Shape a boolean-or-mode resolution for the settings precedence contract. */
|
|
539
|
+
export declare function delegatedWorkerAutoApproveSettings(meshPolicy?: Pick<RepoMeshPolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, nodePolicy?: Pick<RepoMeshNodePolicy, 'delegatedWorkerAutoApprove' | 'delegatedWorkerDangerousModeAllow'> | null, provider?: Pick<ProviderModule, 'autoApproveModes'> | null): {
|
|
540
|
+
autoApprove: boolean | undefined;
|
|
541
|
+
autoApproveMode: string | undefined;
|
|
542
|
+
delegatedWorkerDangerousModeAllow: boolean;
|
|
543
|
+
};
|
|
534
544
|
/**
|
|
535
545
|
* MESH-SEND-KEYS (feature 3): resolve whether DESTRUCTIVE key injection
|
|
536
546
|
* (CTRL_C/ESC via mesh_send_keys) is permitted for a node. Node policy overrides
|
package/dist/shared-types.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type { ProviderState, ProviderStatus, ActiveChatData, IdeProviderState, C
|
|
|
13
13
|
export type { ProviderErrorReason } from './providers/provider-instance.js';
|
|
14
14
|
import type { ActiveChatData as _ActiveChatData, ProviderErrorReason as _ProviderErrorReason } from './providers/provider-instance.js';
|
|
15
15
|
import type { WorkspaceEntry } from './config/workspaces.js';
|
|
16
|
-
import type { ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
|
|
16
|
+
import type { AutoApproveModesConfig, ProviderMeshCoordinatorConfig, ProviderResumeCapability } from './providers/contracts.js';
|
|
17
17
|
import type { GitCompactSummary, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams } from './git/git-types.js';
|
|
18
18
|
import type { InteractivePrompt } from './providers/types/interactive-prompt.js';
|
|
19
19
|
export type { GitCommandName, GitCompactSummary, GitDiffSummary, GitFailureReason, GitFileChange, GitFileChangeStatus, GitRepoIdentity, GitRepoStatus, GitSnapshot, GitSnapshotCompareSummary, GitSnapshotReason, GitWorkspaceUpdate, WorkspaceGitSubscriptionParams, } from './git/git-types.js';
|
|
@@ -428,6 +428,8 @@ export interface AvailableProviderInfo {
|
|
|
428
428
|
lastVerification?: MachineProviderCheckResult;
|
|
429
429
|
/** Provider-declared Repo Mesh coordinator/MCP behavior. */
|
|
430
430
|
meshCoordinator?: ProviderMeshCoordinatorConfig;
|
|
431
|
+
/** Provider-declared auto-approve choices shown by session launch UIs. */
|
|
432
|
+
autoApproveModes?: AutoApproveModesConfig;
|
|
431
433
|
/** BRAIN-ROUTING: suggested model values for the new-session model dropdown. */
|
|
432
434
|
modelOptions?: string[];
|
|
433
435
|
/** BRAIN-ROUTING: reasoning-effort values for the new-session thinking dropdown. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhdev/daemon-core",
|
|
3
|
-
"version": "1.0.18-rc.
|
|
3
|
+
"version": "1.0.18-rc.17",
|
|
4
4
|
"description": "ADHDev daemon core — CDP, IDE detection, providers, command execution",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"author": "vilmire",
|
|
48
48
|
"license": "AGPL-3.0-or-later",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@adhdev/mesh-shared": "1.0.18-rc.
|
|
51
|
-
"@adhdev/session-host-core": "1.0.18-rc.
|
|
50
|
+
"@adhdev/mesh-shared": "1.0.18-rc.17",
|
|
51
|
+
"@adhdev/session-host-core": "1.0.18-rc.17",
|
|
52
52
|
"@agentclientprotocol/sdk": "^0.16.1",
|
|
53
53
|
"ajv": "^8.20.0",
|
|
54
54
|
"ajv-formats": "^3.0.1",
|
|
@@ -34,6 +34,7 @@ import type { SessionRegistry } from '../sessions/registry.js';
|
|
|
34
34
|
import type { ProviderInstance } from '../providers/provider-instance.js';
|
|
35
35
|
import { LOG } from '../logging/logger.js';
|
|
36
36
|
import { shouldRestoreHostedRuntime } from './hosted-runtime-restore.js';
|
|
37
|
+
import { findProviderAutoApproveMode, resolveProviderAutoApproveMode } from '../providers/auto-approve-modes.js';
|
|
37
38
|
|
|
38
39
|
// ─── external dependency interface ──────────────────────────
|
|
39
40
|
|
|
@@ -452,6 +453,40 @@ export function expandThinkingLaunchArgs(
|
|
|
452
453
|
return template.map((part) => part.includes('{{level}}') ? part.replace('{{level}}', mapped) : part);
|
|
453
454
|
}
|
|
454
455
|
|
|
456
|
+
function matchesRemovedLaunchArg(arg: string, removeArg: string): boolean {
|
|
457
|
+
return arg === removeArg || (removeArg.startsWith('--') && arg.startsWith(`${removeArg}=`));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Apply a selected launch-args auto-approve mode without mutating provider metadata.
|
|
462
|
+
* removeArgs only targets provider-owned base spawn.args; launchArgs are prepended to
|
|
463
|
+
* per-launch args beside model/thinking args, making conflict removal order-independent.
|
|
464
|
+
*/
|
|
465
|
+
export function applyAutoApproveModeLaunchArgs(
|
|
466
|
+
provider: ProviderModule | undefined,
|
|
467
|
+
cliArgs: string[] | undefined,
|
|
468
|
+
settings: Record<string, unknown> | undefined,
|
|
469
|
+
): { provider: ProviderModule | undefined; cliArgs: string[] | undefined } {
|
|
470
|
+
if (!provider) return { provider, cliArgs };
|
|
471
|
+
const resolved = resolveProviderAutoApproveMode(provider, settings);
|
|
472
|
+
if (!resolved.active || resolved.strategy !== 'launch-args') return { provider, cliArgs };
|
|
473
|
+
const mode = findProviderAutoApproveMode(provider, resolved.modeId);
|
|
474
|
+
if (!mode || !Array.isArray(mode.launchArgs) || mode.launchArgs.length === 0) return { provider, cliArgs };
|
|
475
|
+
|
|
476
|
+
const removeArgs = Array.isArray(mode.removeArgs) ? mode.removeArgs : [];
|
|
477
|
+
const baseArgs = provider.spawn?.args;
|
|
478
|
+
const filteredBaseArgs = Array.isArray(baseArgs) && removeArgs.length > 0
|
|
479
|
+
? baseArgs.filter((arg) => !removeArgs.some((removeArg) => matchesRemovedLaunchArg(arg, removeArg)))
|
|
480
|
+
: baseArgs;
|
|
481
|
+
const launchProvider = filteredBaseArgs === baseArgs
|
|
482
|
+
? provider
|
|
483
|
+
: { ...provider, spawn: { ...provider.spawn!, args: filteredBaseArgs } };
|
|
484
|
+
return {
|
|
485
|
+
provider: launchProvider,
|
|
486
|
+
cliArgs: [...mode.launchArgs, ...(cliArgs || [])],
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
455
490
|
function readSubcommandSessionId(args: string[], subcommands: string[]): string | undefined {
|
|
456
491
|
const resumeIndex = args.findIndex((arg) => subcommands.includes(arg));
|
|
457
492
|
if (resumeIndex < 0) return undefined;
|
|
@@ -1048,6 +1083,17 @@ export class DaemonCliManager {
|
|
|
1048
1083
|
console.log(colorize('cyan', ` 📦 Using provider: ${provider.name} (${provider.type})`));
|
|
1049
1084
|
}
|
|
1050
1085
|
|
|
1086
|
+
const launchSettings = {
|
|
1087
|
+
...this.providerLoader.getSettings(normalizedType),
|
|
1088
|
+
...(options?.settingsOverride || {}),
|
|
1089
|
+
};
|
|
1090
|
+
const versionResolvedProvider = provider
|
|
1091
|
+
? (this.providerLoader.resolve(cliType, { version: cliInfo.version }) || provider)
|
|
1092
|
+
: undefined;
|
|
1093
|
+
const autoApproveLaunch = applyAutoApproveModeLaunchArgs(versionResolvedProvider, cliArgs, launchSettings);
|
|
1094
|
+
const launchProvider = autoApproveLaunch.provider || provider;
|
|
1095
|
+
const cliArgsWithAutoApprove = autoApproveLaunch.cliArgs;
|
|
1096
|
+
|
|
1051
1097
|
// ─── Model axis (MAGI kind-panel): expand initialModel → launch args ───
|
|
1052
1098
|
// For a plain CLI provider the model is selected at spawn time via the manifest's
|
|
1053
1099
|
// modelLaunchArgs template ('{{model}}' → the requested model). ACP providers took
|
|
@@ -1055,10 +1101,10 @@ export class DaemonCliManager {
|
|
|
1055
1101
|
// or no requested model, is a no-op — model selection is best-effort and must never
|
|
1056
1102
|
// fail a launch. The model args are prepended so a caller's explicit cliArgs (e.g. a
|
|
1057
1103
|
// resume flag) still win positionally where order matters.
|
|
1058
|
-
const modelLaunchArgs = expandModelLaunchArgs(
|
|
1104
|
+
const modelLaunchArgs = expandModelLaunchArgs(launchProvider?.modelLaunchArgs, initialModel);
|
|
1059
1105
|
const cliArgsWithModel = modelLaunchArgs
|
|
1060
|
-
? [...modelLaunchArgs, ...(
|
|
1061
|
-
:
|
|
1106
|
+
? [...modelLaunchArgs, ...(cliArgsWithAutoApprove || [])]
|
|
1107
|
+
: cliArgsWithAutoApprove;
|
|
1062
1108
|
if (initialModel && !modelLaunchArgs) {
|
|
1063
1109
|
LOG.warn('CLI', `[${normalizedType}] initialModel='${initialModel}' requested but provider declares no modelLaunchArgs template — launching without model selection.`);
|
|
1064
1110
|
}
|
|
@@ -1069,7 +1115,7 @@ export class DaemonCliManager {
|
|
|
1069
1115
|
// Best-effort; a provider with no template (or no requested level) is a no-op. ACP
|
|
1070
1116
|
// providers route thinking through setConfigOption('thought_level') above.
|
|
1071
1117
|
const initialThinkingLevel = options?.initialThinkingLevel;
|
|
1072
|
-
const thinkingLaunchArgs = expandThinkingLaunchArgs(
|
|
1118
|
+
const thinkingLaunchArgs = expandThinkingLaunchArgs(launchProvider?.thinkingLaunchArgs, initialThinkingLevel, launchProvider?.thinkingLevelMap);
|
|
1073
1119
|
const cliArgsWithBrain = thinkingLaunchArgs
|
|
1074
1120
|
? [...thinkingLaunchArgs, ...(cliArgsWithModel || [])]
|
|
1075
1121
|
: cliArgsWithModel;
|
|
@@ -1078,13 +1124,13 @@ export class DaemonCliManager {
|
|
|
1078
1124
|
}
|
|
1079
1125
|
|
|
1080
1126
|
// ─── Resolve launch options → provider session binding ───
|
|
1081
|
-
const sessionBinding = resolveCliSessionBinding(
|
|
1127
|
+
const sessionBinding = resolveCliSessionBinding(launchProvider, normalizedType, cliArgsWithBrain, options?.resumeSessionId);
|
|
1082
1128
|
const resolvedCliArgs = sessionBinding.cliArgs;
|
|
1083
1129
|
|
|
1084
1130
|
// If InstanceManager exists, manage as CliProviderInstance unified
|
|
1085
1131
|
const instanceManager = this.deps.getInstanceManager();
|
|
1086
|
-
if (
|
|
1087
|
-
const resolvedProvider =
|
|
1132
|
+
if (launchProvider && instanceManager) {
|
|
1133
|
+
const resolvedProvider = launchProvider;
|
|
1088
1134
|
await this.registerCliInstance(
|
|
1089
1135
|
key,
|
|
1090
1136
|
normalizedType,
|
|
@@ -1092,7 +1138,7 @@ export class DaemonCliManager {
|
|
|
1092
1138
|
resolvedDir,
|
|
1093
1139
|
resolvedCliArgs,
|
|
1094
1140
|
resolvedProvider,
|
|
1095
|
-
|
|
1141
|
+
launchSettings,
|
|
1096
1142
|
false,
|
|
1097
1143
|
{
|
|
1098
1144
|
providerSessionId: sessionBinding.providerSessionId,
|
package/src/index.ts
CHANGED
|
@@ -143,6 +143,8 @@ export type {
|
|
|
143
143
|
export {
|
|
144
144
|
DEFAULT_MESH_POLICY,
|
|
145
145
|
resolveDelegatedWorkerAutoApprove,
|
|
146
|
+
delegatedWorkerAutoApproveSettings,
|
|
147
|
+
resolveDelegatedWorkerDangerousModeAllow,
|
|
146
148
|
resolveAllowSendKeysDestructive,
|
|
147
149
|
resolveMagiSessionCleanupMode,
|
|
148
150
|
magiAutoLaunchedSessionCleanupDecision,
|
|
@@ -489,7 +491,7 @@ export { ProviderInstanceManager } from './providers/provider-instance-manager.j
|
|
|
489
491
|
export { IdeProviderInstance } from './providers/ide-provider-instance.js';
|
|
490
492
|
export { CliProviderInstance } from './providers/cli-provider-instance.js';
|
|
491
493
|
export { AcpProviderInstance } from './providers/acp-provider-instance.js';
|
|
492
|
-
export type { ProviderModule, CdpTargetFilter, ProviderResumeCapability, InputEnvelope, InputPart, MessagePart, ReadChatTurnStatus, ControlListResult, ControlSetResult, ControlInvokeResult } from './providers/contracts.js';
|
|
494
|
+
export type { ProviderModule, AutoApproveMode, AutoApproveModesConfig, AutoApproveModeRisk, AutoApproveModeStrategy, CdpTargetFilter, ProviderResumeCapability, InputEnvelope, InputPart, MessagePart, ReadChatTurnStatus, ControlListResult, ControlSetResult, ControlInvokeResult } from './providers/contracts.js';
|
|
493
495
|
export type { ProviderSourceConfigSnapshot, ProviderSourceConfigUpdate } from './config/provider-source-config.js';
|
|
494
496
|
export { parseProviderSourceConfigUpdate } from './config/provider-source-config.js';
|
|
495
497
|
export { normalizeInputEnvelope, normalizeMessageParts, flattenMessageParts } from './providers/io-contracts.js';
|
|
@@ -26,7 +26,7 @@ import { getActiveDirectDispatches, getQueue } from './mesh-work-queue.js';
|
|
|
26
26
|
import { readLedgerEntries } from './mesh-ledger.js';
|
|
27
27
|
import { pruneStaleDirectDispatches } from './mesh-active-work.js';
|
|
28
28
|
import { reconcileDirectDispatchCompletionFromTranscript } from './mesh-events-stale.js';
|
|
29
|
-
import { extractFinalAssistantSummaryEvidence } from '../providers/chat-message-normalization.js';
|
|
29
|
+
import { extractFinalAssistantSummaryEvidence, hasTrailingToolActivityAfterFinalAssistant } from '../providers/chat-message-normalization.js';
|
|
30
30
|
import type { ChatMessage } from '../types.js';
|
|
31
31
|
import {
|
|
32
32
|
getMeshV2BackstopCounters,
|
|
@@ -483,6 +483,17 @@ export async function pollAssignedTaskTerminalEvidence(
|
|
|
483
483
|
const evidence = extractFinalAssistantSummaryEvidence(messages);
|
|
484
484
|
if (!evidence.finalSummary) return null; // idle but no assistant result yet → not a turn-end
|
|
485
485
|
|
|
486
|
+
// EARLY-IDLE-COMPLETION-FALSE-POSITIVE (poll defense-in-depth): a momentary idle read
|
|
487
|
+
// (startup-grace, or the sliver between an assistant preamble and the tool it fires) can
|
|
488
|
+
// show an assistant "Let me explore…" bubble FOLLOWED by trailing Read/Grep tool_use — a
|
|
489
|
+
// turn that is still executing, not a turn-end. selectFinalAssistantTurnEndMessage skips
|
|
490
|
+
// those trailing tool bubbles and would promote the preamble, so guard here: if a
|
|
491
|
+
// tool/terminal activity bubble trails the final assistant message, the worker is mid-turn
|
|
492
|
+
// — refuse the completion (fall through to the caller's reclaim/grace path). A genuinely
|
|
493
|
+
// finished pure-PTY worker ends on its final assistant with no trailing tool activity, so
|
|
494
|
+
// the kimi rescue is preserved.
|
|
495
|
+
if (hasTrailingToolActivityAfterFinalAssistant(messages)) return null;
|
|
496
|
+
|
|
486
497
|
// Stale-summary guard (same bar as PHASE 4): a reused session's transcript tail may hold a
|
|
487
498
|
// PRIOR task's summary. Require the final assistant message to be dated at/after THIS task's
|
|
488
499
|
// dispatch. When either timestamp is unusable, do NOT short-circuit — fall through so we never
|
|
@@ -16,7 +16,7 @@ import { resolveMeshHostStatus } from './mesh-host-ownership.js';
|
|
|
16
16
|
import { enqueueUnresolvedDelegateForward, nudgeUnresolvedForwardRetry } from './mesh-unresolved-forward-outbox.js';
|
|
17
17
|
import { traceMeshEventStage, traceMeshEventDrop } from './mesh-event-trace.js';
|
|
18
18
|
import { getLastDisplayMessage } from '../status/snapshot.js';
|
|
19
|
-
import {
|
|
19
|
+
import { delegatedWorkerAutoApproveSettings } from '../repo-mesh-types.js';
|
|
20
20
|
import { meshNodeIdMatches, daemonIdsEquivalent, expandDaemonIdForms, sessionIdsEquivalent, withStatusProbeMarker, type MeshNodeIdentified } from '@adhdev/mesh-shared';
|
|
21
21
|
import {
|
|
22
22
|
findRecentTerminalLedgerEvidence,
|
|
@@ -1614,7 +1614,11 @@ function injectMeshSystemMessage(components: DaemonComponents, args: {
|
|
|
1614
1614
|
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || 'hidden',
|
|
1615
1615
|
// Coordinator-dispatched recovery relaunch: same auto-approve
|
|
1616
1616
|
// policy as the primary worker launch path.
|
|
1617
|
-
|
|
1617
|
+
...delegatedWorkerAutoApproveSettings(
|
|
1618
|
+
mesh?.policy,
|
|
1619
|
+
node?.policy,
|
|
1620
|
+
components.providerLoader?.getMeta(recoveryContext.failedProviderType),
|
|
1621
|
+
),
|
|
1618
1622
|
launchedByCoordinator: true,
|
|
1619
1623
|
}
|
|
1620
1624
|
}).catch((e: any) => LOG.error('MeshRecovery', `Failed to auto-relaunch session for ${node.id}: ${e?.message}`));
|
|
@@ -13,7 +13,7 @@ import { createSessionDelivery, updateSessionDeliveryStatus } from './mesh-deliv
|
|
|
13
13
|
import { MeshRuntimeStore } from './mesh-runtime-store.js';
|
|
14
14
|
import { traceMeshEventDrop } from './mesh-event-trace.js';
|
|
15
15
|
import { awaitWithWarmupDeadline, resolveWarmupDeadlineOpts } from './mesh-warmup-deadline.js';
|
|
16
|
-
import {
|
|
16
|
+
import { delegatedWorkerAutoApproveSettings, resolveProviderMaxParallel, resolveNodeSchedulingPriority, normalizeMeshSchedulingStrategy, resolveMaxParallelTasks, resolveMaxReadonlyParallelTasks, resolveCoordinatorIdlePushPolicy } from '../repo-mesh-types.js';
|
|
17
17
|
import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
|
|
18
18
|
import { normalizeMeshNodeId, meshNodeIdMatches, daemonIdsEquivalent, canonicalDaemonId, expandDaemonIdForms, normalizeMeshWorkspaceForCompare, meshWorkspacesEquivalent, sessionIdsEquivalent, normalizeNodeCapabilitySlots, isMeshTaskDifficulty, withStatusProbeMarker, type MeshNodeIdentified, type NodeCapabilitySlot, type MeshTaskDifficulty } from '@adhdev/mesh-shared';
|
|
19
19
|
import { resolveNodeCapabilitySlots } from './mesh-node-slots.js';
|
|
@@ -690,7 +690,7 @@ export function tryAssignQueueTask(
|
|
|
690
690
|
if (inst && typeof inst.updateSettings === 'function') {
|
|
691
691
|
// Adopting a (possibly manually-opened) local session as a worker: apply the
|
|
692
692
|
// delegated-worker auto-approve policy here too, so a session that was launched
|
|
693
|
-
// without
|
|
693
|
+
// without an auto-approve boolean/mode still resolves the delegated policy once dispatched
|
|
694
694
|
// to it (the "approval notification fires only for certain delegated sessions"
|
|
695
695
|
// case). updateSettings preserves runtime mesh keys; passing autoApprove keeps it.
|
|
696
696
|
//
|
|
@@ -704,7 +704,11 @@ export function tryAssignQueueTask(
|
|
|
704
704
|
meshNodeFor: meshId,
|
|
705
705
|
meshNodeId: nodeId,
|
|
706
706
|
launchedByCoordinator: true,
|
|
707
|
-
|
|
707
|
+
...delegatedWorkerAutoApproveSettings(
|
|
708
|
+
mesh?.policy,
|
|
709
|
+
node?.policy,
|
|
710
|
+
components.providerLoader?.getMeta(providerType),
|
|
711
|
+
),
|
|
708
712
|
...(localDaemonId ? { meshCoordinatorDaemonId: localDaemonId } : {}),
|
|
709
713
|
// COMPLETION-PROPAGATION F5: (re)stamp the coordinator SESSION anchor from THIS
|
|
710
714
|
// task's sourceCoordinatorSessionId with PRIORITY — a manually-launched (or reused)
|
|
@@ -2318,8 +2322,12 @@ async function maybeAutoLaunchOneQueueSession(components: DaemonComponents, mesh
|
|
|
2318
2322
|
spawnedSessionVisibility: mesh?.policy?.spawnedSessionVisibility || 'hidden',
|
|
2319
2323
|
// Coordinator-dispatched worker: auto-approve unless mesh/node policy
|
|
2320
2324
|
// opts out (default true). Lands in settingsOverride and beats the
|
|
2321
|
-
// global per-provider-type
|
|
2322
|
-
|
|
2325
|
+
// global per-provider-type boolean/mode through explicit opposite-key clearing.
|
|
2326
|
+
...delegatedWorkerAutoApproveSettings(
|
|
2327
|
+
mesh?.policy,
|
|
2328
|
+
node?.policy,
|
|
2329
|
+
components.providerLoader?.getMeta(resolved.providerType),
|
|
2330
|
+
),
|
|
2323
2331
|
launchedByCoordinator: true,
|
|
2324
2332
|
autoLaunchedForQueueTaskId: task.id,
|
|
2325
2333
|
};
|
|
@@ -2984,4 +2992,3 @@ export function runIdleMaintenanceThenAssignQueue(components: DaemonComponents,
|
|
|
2984
2992
|
});
|
|
2985
2993
|
});
|
|
2986
2994
|
}
|
|
2987
|
-
|
|
@@ -69,13 +69,15 @@ import {
|
|
|
69
69
|
resolveCoordinatorDaemonIds,
|
|
70
70
|
daemonHostsMesh,
|
|
71
71
|
resolveCoordinatorSelfIds,
|
|
72
|
+
daemonIdListIncludes,
|
|
72
73
|
} from './mesh-reconcile-identity.js';
|
|
73
74
|
import {
|
|
74
75
|
resolveAutoPruneMinAgeMs,
|
|
75
76
|
resolvePendingHeldDrainEscalateMs,
|
|
76
77
|
resolveReconcileIntervalMs,
|
|
77
78
|
} from './mesh-reconcile-config.js';
|
|
78
|
-
import { pullRemoteNodeQueues, pullPendingEventsFromNode } from './mesh-remote-event-pull.js';
|
|
79
|
+
import { pullRemoteNodeQueues, pullPendingEventsFromNode, reprobeWorkerStatus } from './mesh-remote-event-pull.js';
|
|
80
|
+
import { isPurePtyTranscriptProvider } from '../cli-adapters/provider-cli-shared.js';
|
|
79
81
|
import { runDiskRetentionSweep, detectAndSignalOrphanWorktrees } from './mesh-disk-retention.js';
|
|
80
82
|
import {
|
|
81
83
|
reconcileUnterminatedDirectDispatches,
|
|
@@ -787,6 +789,118 @@ function assignedRowLiveStatusIsAwaitingApproval(
|
|
|
787
789
|
}
|
|
788
790
|
}
|
|
789
791
|
|
|
792
|
+
// EARLY-IDLE-COMPLETION-FALSE-POSITIVE — resolve whether the assigned session's provider is
|
|
793
|
+
// the pure-PTY transcript class (kimi and kin — no native history, no provider authority), from
|
|
794
|
+
// the LOCAL instance when it is present. Returns:
|
|
795
|
+
// true → local instance is a pure-PTY provider (the class the early rescue exists for)
|
|
796
|
+
// false → local instance is NOT pure-PTY (native / provider-authoritative)
|
|
797
|
+
// undefined → no local instance (remote worker / gone / id-form skew) — pure-PTY unknowable here
|
|
798
|
+
// A remote pure-PTY worker is handled by the reprobe path (a fresh-idle read positively confirms
|
|
799
|
+
// the session is settled before the streak accrues), not by this local-only capability check.
|
|
800
|
+
function resolveLocalSessionPurePty(components: DaemonComponents, sessionId: string): boolean | undefined {
|
|
801
|
+
try {
|
|
802
|
+
const instances = components.instanceManager?.getByCategory?.('cli') || [];
|
|
803
|
+
const inst = instances.find((i: any) => {
|
|
804
|
+
const sid = readNonEmptyString(i?.getState?.().instanceId);
|
|
805
|
+
return sid && sessionIdsEquivalent(sid, sessionId);
|
|
806
|
+
}) as { provider?: unknown } | undefined;
|
|
807
|
+
if (!inst) return undefined; // remote / gone / id-form skew — not locally observable
|
|
808
|
+
const provider = inst.provider;
|
|
809
|
+
if (!provider || typeof provider !== 'object') return undefined;
|
|
810
|
+
return isPurePtyTranscriptProvider(provider as Parameters<typeof isPurePtyTranscriptProvider>[0]);
|
|
811
|
+
} catch {
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// EARLY-IDLE-COMPLETION-FALSE-POSITIVE — decide whether the early transcript-evidence completion
|
|
817
|
+
// streak may ACCRUE this tick for an assigned row. This hardens the original arm gate, which only
|
|
818
|
+
// checked `resolveSessionBusyVerdict(...) !== 'GENERATING'` — a gate a REMOTE worker (local verdict
|
|
819
|
+
// UNKNOWN, not GENERATING) passed unconditionally, so its "8s continuous idle" streak degenerated to
|
|
820
|
+
// 8s of wall-clock even while the worker was genuinely mid-turn, and a startup-grace boot-window idle
|
|
821
|
+
// (before the first turn ever started) also passed. The row was then early-completed off a preamble.
|
|
822
|
+
//
|
|
823
|
+
// Two added requirements, defense-in-depth on top of the existing confirmed-delivery / no-terminal-
|
|
824
|
+
// ledger / bound-session gate the caller applies:
|
|
825
|
+
// (a) POSITIVE idle evidence, not merely "not GENERATING". A LOCAL IDLE_CONFIRMED verdict qualifies
|
|
826
|
+
// directly. A remote/UNKNOWN verdict is RE-PROBED with a fresh read_chat status this tick: only
|
|
827
|
+
// a definitively 'idle' read lets the streak accrue; any active status (generating/waiting/…),
|
|
828
|
+
// or an inconclusive read, resets it. This is what stops the remote worker's mid-turn busy from
|
|
829
|
+
// silently passing as "continuous idle".
|
|
830
|
+
// (b) TURN-START evidence, so a boot-window / never-consumed idle cannot early-complete a preamble.
|
|
831
|
+
// taskDeliveryConsumed===true (the worker emitted agent:generating_started) satisfies it. When
|
|
832
|
+
// the delivery was never consumed, only a LOCAL pure-PTY provider — the exact class that never
|
|
833
|
+
// emits generating_started and that this rescue exists for — is still allowed; a native/
|
|
834
|
+
// provider-authoritative worker that hasn't started its turn is NOT armed (it will complete via
|
|
835
|
+
// its own emit or the normal grace). A remote-not-consumed worker whose provider class is
|
|
836
|
+
// unknowable locally falls back to the reprobe: it may accrue only once (a) reads it positively
|
|
837
|
+
// idle, and the poll's post-dispatch + trailing-tool-activity guards remain the final net.
|
|
838
|
+
async function evaluateEarlyIdleTranscriptArm(
|
|
839
|
+
components: DaemonComponents,
|
|
840
|
+
mesh: { id: string; nodes?: Array<{ id: string; daemonId?: string; workspace?: string }> },
|
|
841
|
+
store: MeshRuntimeStore,
|
|
842
|
+
row: { id: string; assignedSessionId?: string; assignedNodeId?: string; assignedProviderType?: string },
|
|
843
|
+
localDaemonId?: string,
|
|
844
|
+
selfIds: string[] = [],
|
|
845
|
+
): Promise<boolean> {
|
|
846
|
+
const sessionId = readNonEmptyString(row.assignedSessionId);
|
|
847
|
+
if (!sessionId) return false;
|
|
848
|
+
|
|
849
|
+
// (a) POSITIVE idle evidence.
|
|
850
|
+
const verdict = resolveSessionBusyVerdict(components, sessionId);
|
|
851
|
+
if (verdict === 'GENERATING') return false; // locally observed mid-turn — never accrue
|
|
852
|
+
if (verdict === 'UNKNOWN') {
|
|
853
|
+
// Remote / gone / id-form skew. First consult the LIVE mesh node snapshot (the same
|
|
854
|
+
// cross-daemon status feed the approval-hold guard and mesh_status use): if it already
|
|
855
|
+
// reports a definitively NON-idle status (generating / waiting_approval / awaiting_choice /
|
|
856
|
+
// …), the worker is not settled — reset the streak WITHOUT a read_chat. This keeps the
|
|
857
|
+
// streak the cheap per-tick gate for the common case and avoids probing a worker the graph
|
|
858
|
+
// already knows is busy or parked at an approval.
|
|
859
|
+
const nodeId = readNonEmptyString(row.assignedNodeId);
|
|
860
|
+
const node = (mesh.nodes ?? []).find(n => n.id === nodeId);
|
|
861
|
+
const liveStatus = nodeId
|
|
862
|
+
? readNonEmptyString(sessionStatusFromNodes(mesh.nodes, nodeId, sessionId).status).toLowerCase()
|
|
863
|
+
: '';
|
|
864
|
+
if (liveStatus && liveStatus !== 'idle') return false; // live graph says non-idle → not a turn-end
|
|
865
|
+
if (!liveStatus) {
|
|
866
|
+
// No live snapshot for this session — re-probe the worker's own status this tick so a
|
|
867
|
+
// genuinely busy remote worker breaks the streak instead of coasting through as "not
|
|
868
|
+
// GENERATING". getInstance may be absent on some component surfaces (tests / standalone),
|
|
869
|
+
// so guard it.
|
|
870
|
+
const nodeDaemonId = readNonEmptyString(node?.daemonId);
|
|
871
|
+
const isLocalNode = !nodeDaemonId
|
|
872
|
+
|| daemonIdsEquivalent(nodeDaemonId, localDaemonId)
|
|
873
|
+
|| daemonIdListIncludes(selfIds, nodeDaemonId)
|
|
874
|
+
|| !!components.instanceManager?.getInstance?.(sessionId);
|
|
875
|
+
const providerType = readNonEmptyString(row.assignedProviderType);
|
|
876
|
+
const readArgs: Record<string, unknown> = {
|
|
877
|
+
sessionId,
|
|
878
|
+
targetSessionId: sessionId,
|
|
879
|
+
tailLimit: 1,
|
|
880
|
+
...(node?.workspace ? { workspace: node.workspace } : {}),
|
|
881
|
+
...(providerType ? { agentType: providerType, providerType } : {}),
|
|
882
|
+
};
|
|
883
|
+
const probedStatus = await reprobeWorkerStatus(components, { isLocalNode, nodeDaemonId, readArgs });
|
|
884
|
+
// Only a positively-idle re-probe lets the streak accrue. A non-idle status (the worker
|
|
885
|
+
// IS busy) or an inconclusive read (null — transport error / no payload) resets it: we
|
|
886
|
+
// never treat "couldn't tell" as idle for this early-completion path.
|
|
887
|
+
if (probedStatus !== 'idle') return false;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// (b) TURN-START evidence (guards the boot-window / preamble false positive).
|
|
892
|
+
if (store.taskDeliveryConsumed(mesh.id, row.id)) return true; // worker emitted generating_started
|
|
893
|
+
// Delivery never consumed. Preserve the kimi rescue ONLY for the LOCAL pure-PTY class (which
|
|
894
|
+
// never emits generating_started); a native/provider-authoritative local worker that hasn't
|
|
895
|
+
// started its turn must NOT be armed off a boot-window idle. For a remote worker (pure-PTY
|
|
896
|
+
// unknowable here) the positive-idle reprobe in (a) is the gate — allow accrual and let the poll
|
|
897
|
+
// enforce post-dispatch + trailing-tool-activity finality.
|
|
898
|
+
const localPurePty = resolveLocalSessionPurePty(components, sessionId);
|
|
899
|
+
if (localPurePty === true) return true; // local pure-PTY — the class this rescue targets
|
|
900
|
+
if (localPurePty === false) return false; // local native/provider-owned, turn not started — hold
|
|
901
|
+
return verdict === 'UNKNOWN'; // remote, unknowable class — trust the (a) reprobe
|
|
902
|
+
}
|
|
903
|
+
|
|
790
904
|
// PHASE 2.5 — assigned-stranded dispatch watchdog (Bug B). claimNextTask atomically
|
|
791
905
|
// flips a row to 'assigned' BEFORE the fire-and-forget dispatch runs. If that dispatch
|
|
792
906
|
// neither rejects (→ no .catch requeue) nor is confirmed delivered — a relay that hangs
|
|
@@ -840,12 +954,18 @@ async function recoverStrandedAssignedDispatches(
|
|
|
840
954
|
// elapses. pollAssignedTaskTerminalEvidence enforces idle + final-assistant-after-dispatch, so
|
|
841
955
|
// a mid-turn / stale-tail / unreadable worker is never falsely completed — a non-qualifying
|
|
842
956
|
// tick clears the streak below.
|
|
843
|
-
|
|
957
|
+
// EARLY-IDLE-COMPLETION-FALSE-POSITIVE: the arm gate now requires POSITIVE idle evidence
|
|
958
|
+
// (a fresh-idle reprobe for a remote/UNKNOWN worker, not merely "not GENERATING") AND
|
|
959
|
+
// turn-start evidence (delivery consumed, or the local pure-PTY class this rescue targets),
|
|
960
|
+
// so a mid-turn remote worker or a boot-window preamble can no longer coast the continuous-
|
|
961
|
+
// idle streak to a false completion. See evaluateEarlyIdleTranscriptArm.
|
|
962
|
+
const earlyArm =
|
|
844
963
|
store.taskHasConfirmedDelivery(meshId, row.id)
|
|
845
964
|
&& !findTerminalLedgerEvidenceForTask({ meshId, taskId: row.id })
|
|
846
965
|
&& readNonEmptyString(row.assignedSessionId)
|
|
847
|
-
|
|
848
|
-
|
|
966
|
+
? await evaluateEarlyIdleTranscriptArm(components, mesh, store, row, localDaemonId, selfIds)
|
|
967
|
+
: false;
|
|
968
|
+
if (earlyArm) {
|
|
849
969
|
const since = assignedIdleFinalAssistantSince.get(idleTranscriptStreakKey);
|
|
850
970
|
if (since === undefined) {
|
|
851
971
|
assignedIdleFinalAssistantSince.set(idleTranscriptStreakKey, nowMs);
|