@soleri/core 9.5.0 → 9.7.0
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/adapters/claude-code-adapter.d.ts +27 -0
- package/dist/adapters/claude-code-adapter.d.ts.map +1 -0
- package/dist/adapters/claude-code-adapter.js +111 -0
- package/dist/adapters/claude-code-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +10 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/registry.d.ts +21 -0
- package/dist/adapters/registry.d.ts.map +1 -0
- package/dist/adapters/registry.js +44 -0
- package/dist/adapters/registry.js.map +1 -0
- package/dist/adapters/types.d.ts +93 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +10 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/brain/brain.d.ts +12 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +106 -44
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +36 -30
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/chat/agent-loop.js +1 -1
- package/dist/chat/agent-loop.js.map +1 -1
- package/dist/chat/notifications.d.ts.map +1 -1
- package/dist/chat/notifications.js +4 -0
- package/dist/chat/notifications.js.map +1 -1
- package/dist/control/intent-router.d.ts +1 -0
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +11 -5
- package/dist/control/intent-router.js.map +1 -1
- package/dist/curator/curator.d.ts +4 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +141 -27
- package/dist/curator/curator.js.map +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/llm-client.d.ts.map +1 -1
- package/dist/llm/llm-client.js +1 -0
- package/dist/llm/llm-client.js.map +1 -1
- package/dist/packs/index.d.ts +3 -2
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +3 -2
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/lockfile.d.ts +23 -1
- package/dist/packs/lockfile.d.ts.map +1 -1
- package/dist/packs/lockfile.js +50 -4
- package/dist/packs/lockfile.js.map +1 -1
- package/dist/packs/pack-installer.d.ts +10 -0
- package/dist/packs/pack-installer.d.ts.map +1 -1
- package/dist/packs/pack-installer.js +69 -2
- package/dist/packs/pack-installer.js.map +1 -1
- package/dist/packs/pack-lifecycle.d.ts +50 -0
- package/dist/packs/pack-lifecycle.d.ts.map +1 -0
- package/dist/packs/pack-lifecycle.js +91 -0
- package/dist/packs/pack-lifecycle.js.map +1 -0
- package/dist/packs/types.d.ts +76 -29
- package/dist/packs/types.d.ts.map +1 -1
- package/dist/packs/types.js +9 -0
- package/dist/packs/types.js.map +1 -1
- package/dist/persistence/sqlite-provider.d.ts +5 -1
- package/dist/persistence/sqlite-provider.d.ts.map +1 -1
- package/dist/persistence/sqlite-provider.js +22 -2
- package/dist/persistence/sqlite-provider.js.map +1 -1
- package/dist/planning/github-projection.d.ts +11 -9
- package/dist/planning/github-projection.d.ts.map +1 -1
- package/dist/planning/github-projection.js +47 -43
- package/dist/planning/github-projection.js.map +1 -1
- package/dist/planning/goal-ancestry.d.ts +72 -0
- package/dist/planning/goal-ancestry.d.ts.map +1 -0
- package/dist/planning/goal-ancestry.js +137 -0
- package/dist/planning/goal-ancestry.js.map +1 -0
- package/dist/planning/plan-lifecycle.d.ts +2 -0
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +1 -0
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner-types.d.ts +2 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/plugins/types.d.ts +21 -21
- package/dist/queue/pipeline-runner.d.ts.map +1 -1
- package/dist/queue/pipeline-runner.js +4 -0
- package/dist/queue/pipeline-runner.js.map +1 -1
- package/dist/runtime/context-health.d.ts +14 -1
- package/dist/runtime/context-health.d.ts.map +1 -1
- package/dist/runtime/context-health.js +30 -2
- package/dist/runtime/context-health.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
- package/dist/runtime/curator-extra-ops.js +9 -1
- package/dist/runtime/curator-extra-ops.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +169 -0
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +133 -4
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +128 -90
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +44 -11
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/shutdown-registry.d.ts +36 -0
- package/dist/runtime/shutdown-registry.d.ts.map +1 -0
- package/dist/runtime/shutdown-registry.js +74 -0
- package/dist/runtime/shutdown-registry.js.map +1 -0
- package/dist/runtime/types.d.ts +10 -1
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/session/compaction-evaluator.d.ts +20 -0
- package/dist/session/compaction-evaluator.d.ts.map +1 -0
- package/dist/session/compaction-evaluator.js +73 -0
- package/dist/session/compaction-evaluator.js.map +1 -0
- package/dist/session/compaction-policy.d.ts +50 -0
- package/dist/session/compaction-policy.d.ts.map +1 -0
- package/dist/session/compaction-policy.js +17 -0
- package/dist/session/compaction-policy.js.map +1 -0
- package/dist/session/handoff-renderer.d.ts +22 -0
- package/dist/session/handoff-renderer.d.ts.map +1 -0
- package/dist/session/handoff-renderer.js +49 -0
- package/dist/session/handoff-renderer.js.map +1 -0
- package/dist/session/index.d.ts +6 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +5 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/policy-resolver.d.ts +20 -0
- package/dist/session/policy-resolver.d.ts.map +1 -0
- package/dist/session/policy-resolver.js +28 -0
- package/dist/session/policy-resolver.js.map +1 -0
- package/dist/skills/sync-skills.d.ts +27 -0
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +92 -1
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/skills/trust-classifier.d.ts +32 -0
- package/dist/skills/trust-classifier.d.ts.map +1 -0
- package/dist/skills/trust-classifier.js +109 -0
- package/dist/skills/trust-classifier.js.map +1 -0
- package/dist/subagent/concurrency-manager.d.ts +29 -0
- package/dist/subagent/concurrency-manager.d.ts.map +1 -0
- package/dist/subagent/concurrency-manager.js +73 -0
- package/dist/subagent/concurrency-manager.js.map +1 -0
- package/dist/subagent/dispatcher.d.ts +45 -0
- package/dist/subagent/dispatcher.d.ts.map +1 -0
- package/dist/subagent/dispatcher.js +271 -0
- package/dist/subagent/dispatcher.js.map +1 -0
- package/dist/subagent/index.d.ts +14 -0
- package/dist/subagent/index.d.ts.map +1 -0
- package/dist/subagent/index.js +15 -0
- package/dist/subagent/index.js.map +1 -0
- package/dist/subagent/orphan-reaper.d.ts +37 -0
- package/dist/subagent/orphan-reaper.d.ts.map +1 -0
- package/dist/subagent/orphan-reaper.js +71 -0
- package/dist/subagent/orphan-reaper.js.map +1 -0
- package/dist/subagent/result-aggregator.d.ts +7 -0
- package/dist/subagent/result-aggregator.d.ts.map +1 -0
- package/dist/subagent/result-aggregator.js +57 -0
- package/dist/subagent/result-aggregator.js.map +1 -0
- package/dist/subagent/task-checkout.d.ts +36 -0
- package/dist/subagent/task-checkout.d.ts.map +1 -0
- package/dist/subagent/task-checkout.js +52 -0
- package/dist/subagent/task-checkout.js.map +1 -0
- package/dist/subagent/types.d.ts +114 -0
- package/dist/subagent/types.d.ts.map +1 -0
- package/dist/subagent/types.js +9 -0
- package/dist/subagent/types.js.map +1 -0
- package/dist/subagent/workspace-resolver.d.ts +35 -0
- package/dist/subagent/workspace-resolver.d.ts.map +1 -0
- package/dist/subagent/workspace-resolver.js +99 -0
- package/dist/subagent/workspace-resolver.js.map +1 -0
- package/dist/transport/http-server.d.ts.map +1 -1
- package/dist/transport/http-server.js +49 -3
- package/dist/transport/http-server.js.map +1 -1
- package/dist/transport/ws-server.d.ts.map +1 -1
- package/dist/transport/ws-server.js +7 -0
- package/dist/transport/ws-server.js.map +1 -1
- package/dist/vault/linking.d.ts +3 -4
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +79 -32
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-maintenance.d.ts.map +1 -1
- package/dist/vault/vault-maintenance.js +7 -14
- package/dist/vault/vault-maintenance.js.map +1 -1
- package/dist/vault/vault-memories.d.ts.map +1 -1
- package/dist/vault/vault-memories.js +19 -9
- package/dist/vault/vault-memories.js.map +1 -1
- package/dist/vault/vault-schema.d.ts +1 -0
- package/dist/vault/vault-schema.d.ts.map +1 -1
- package/dist/vault/vault-schema.js +20 -0
- package/dist/vault/vault-schema.js.map +1 -1
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +7 -3
- package/dist/vault/vault.js.map +1 -1
- package/package.json +5 -2
- package/src/__tests__/adapters/claude-code-adapter.test.ts +167 -0
- package/src/__tests__/adapters/registry.test.ts +100 -0
- package/src/__tests__/packs/pack-lifecycle.test.ts +379 -0
- package/src/__tests__/subagent/concurrency-manager.test.ts +132 -0
- package/src/__tests__/subagent/dispatcher.test.ts +195 -0
- package/src/__tests__/subagent/orphan-reaper.test.ts +141 -0
- package/src/__tests__/subagent/result-aggregator.test.ts +141 -0
- package/src/__tests__/subagent/task-checkout.test.ts +86 -0
- package/src/__tests__/subagent/workspace-resolver.test.ts +138 -0
- package/src/adapters/claude-code-adapter.ts +163 -0
- package/src/adapters/index.ts +22 -0
- package/src/adapters/registry.ts +53 -0
- package/src/adapters/types.ts +114 -0
- package/src/curator/curator.ts +1 -0
- package/src/index.ts +78 -1
- package/src/packs/index.ts +9 -1
- package/src/packs/lockfile.ts +70 -5
- package/src/packs/pack-installer.ts +78 -2
- package/src/packs/pack-lifecycle.ts +115 -0
- package/src/packs/pack-lockfile.test.ts +1 -1
- package/src/packs/pack-system.test.ts +1 -1
- package/src/packs/types.ts +72 -2
- package/src/persistence/sqlite-provider.ts +26 -2
- package/src/planning/github-projection.ts +6 -0
- package/src/planning/goal-ancestry.test.ts +427 -0
- package/src/planning/goal-ancestry.ts +187 -0
- package/src/planning/plan-lifecycle.ts +3 -0
- package/src/planning/planner-types.ts +2 -0
- package/src/runtime/admin-setup-ops.test.ts +9 -4
- package/src/runtime/context-health.ts +42 -2
- package/src/runtime/orchestrate-ops.ts +153 -1
- package/src/runtime/runtime.ts +15 -0
- package/src/runtime/session-briefing.test.ts +94 -2
- package/src/runtime/session-briefing.ts +48 -12
- package/src/runtime/types.ts +6 -0
- package/src/session/compaction-evaluator.ts +87 -0
- package/src/session/compaction-policy.ts +66 -0
- package/src/session/compaction.test.ts +259 -0
- package/src/session/handoff-renderer.ts +56 -0
- package/src/session/index.ts +12 -0
- package/src/session/policy-resolver.ts +34 -0
- package/src/skills/sync-skills.ts +114 -1
- package/src/skills/trust-classifier.test.ts +252 -0
- package/src/skills/trust-classifier.ts +127 -0
- package/src/subagent/concurrency-manager.ts +89 -0
- package/src/subagent/dispatcher.ts +342 -0
- package/src/subagent/index.ts +28 -0
- package/src/subagent/orphan-reaper.ts +82 -0
- package/src/subagent/result-aggregator.ts +66 -0
- package/src/subagent/task-checkout.ts +60 -0
- package/src/subagent/types.ts +138 -0
- package/src/subagent/workspace-resolver.ts +117 -0
- package/src/vault/vault-scaling.test.ts +3 -2
- package/vitest.config.ts +2 -0
- package/src/hooks/index.ts +0 -6
|
@@ -4,8 +4,18 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Heuristic: tool calls x average payload size x 1.5 overhead factor.
|
|
6
6
|
* Assumes ~200K token context window for fill estimation.
|
|
7
|
+
*
|
|
8
|
+
* Also integrates with CompactionPolicy to trigger session rotation
|
|
9
|
+
* when policy thresholds are breached.
|
|
7
10
|
*/
|
|
8
11
|
|
|
12
|
+
import type {
|
|
13
|
+
CompactionPolicy,
|
|
14
|
+
CompactionResult,
|
|
15
|
+
SessionState,
|
|
16
|
+
} from '../session/compaction-policy.js';
|
|
17
|
+
import { shouldCompact } from '../session/compaction-evaluator.js';
|
|
18
|
+
|
|
9
19
|
// =============================================================================
|
|
10
20
|
// TYPES
|
|
11
21
|
// =============================================================================
|
|
@@ -18,6 +28,8 @@ export interface ContextHealthStatus {
|
|
|
18
28
|
toolCallCount: number;
|
|
19
29
|
estimatedTokens: number;
|
|
20
30
|
recommendation: string;
|
|
31
|
+
/** When a compaction policy is set, this contains the evaluation result. */
|
|
32
|
+
compaction?: CompactionResult;
|
|
21
33
|
}
|
|
22
34
|
|
|
23
35
|
export interface TrackEvent {
|
|
@@ -47,6 +59,17 @@ const RECOMMENDATIONS: Record<HealthLevel, string> = {
|
|
|
47
59
|
export class ContextHealthMonitor {
|
|
48
60
|
private toolCallCount = 0;
|
|
49
61
|
private totalPayloadSize = 0;
|
|
62
|
+
private compactionPolicy: CompactionPolicy | undefined;
|
|
63
|
+
private sessionStartedAt: string | undefined;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set the compaction policy and session start time.
|
|
67
|
+
* When set, `check()` will evaluate compaction thresholds.
|
|
68
|
+
*/
|
|
69
|
+
setCompactionPolicy(policy: CompactionPolicy, startedAt?: string): void {
|
|
70
|
+
this.compactionPolicy = policy;
|
|
71
|
+
this.sessionStartedAt = startedAt ?? new Date().toISOString();
|
|
72
|
+
}
|
|
50
73
|
|
|
51
74
|
/** Track a tool call event. */
|
|
52
75
|
track(event: TrackEvent): void {
|
|
@@ -54,19 +77,36 @@ export class ContextHealthMonitor {
|
|
|
54
77
|
this.totalPayloadSize += event.payloadSize;
|
|
55
78
|
}
|
|
56
79
|
|
|
57
|
-
/** Check current context health status. */
|
|
80
|
+
/** Check current context health status (including compaction policy). */
|
|
58
81
|
check(): ContextHealthStatus {
|
|
59
82
|
const estimatedTokens = Math.round(this.totalPayloadSize * OVERHEAD_FACTOR);
|
|
60
83
|
const estimatedFill = Math.min(estimatedTokens / CONTEXT_WINDOW, 1.0);
|
|
61
84
|
const level = this.classifyLevel(estimatedFill);
|
|
62
85
|
|
|
63
|
-
|
|
86
|
+
const status: ContextHealthStatus = {
|
|
64
87
|
level,
|
|
65
88
|
estimatedFill: Math.round(estimatedFill * 1000) / 1000,
|
|
66
89
|
toolCallCount: this.toolCallCount,
|
|
67
90
|
estimatedTokens,
|
|
68
91
|
recommendation: RECOMMENDATIONS[level],
|
|
69
92
|
};
|
|
93
|
+
|
|
94
|
+
// Evaluate compaction policy if configured
|
|
95
|
+
if (this.compactionPolicy && this.sessionStartedAt) {
|
|
96
|
+
const session: SessionState = {
|
|
97
|
+
runCount: this.toolCallCount,
|
|
98
|
+
inputTokens: estimatedTokens,
|
|
99
|
+
startedAt: this.sessionStartedAt,
|
|
100
|
+
};
|
|
101
|
+
status.compaction = shouldCompact(session, this.compactionPolicy);
|
|
102
|
+
|
|
103
|
+
// Escalate recommendation when compaction is triggered
|
|
104
|
+
if (status.compaction.compact) {
|
|
105
|
+
status.recommendation = `Compaction triggered: ${status.compaction.reason}`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return status;
|
|
70
110
|
}
|
|
71
111
|
|
|
72
112
|
/** Reset all tracking (on session clear). */
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import { detectRationalizations } from '../planning/rationalization-detector.js';
|
|
39
39
|
import { ImpactAnalyzer } from '../planning/impact-analyzer.js';
|
|
40
40
|
import type { ImpactReport } from '../planning/impact-analyzer.js';
|
|
41
|
+
import { recordPlanFeedback } from './plan-feedback-helper.js';
|
|
41
42
|
|
|
42
43
|
// ---------------------------------------------------------------------------
|
|
43
44
|
// Intent detection — keyword-based mapping from prompt to intent
|
|
@@ -235,7 +236,7 @@ export function createOrchestrateOps(
|
|
|
235
236
|
runtime: AgentRuntime,
|
|
236
237
|
facades?: FacadeConfig[],
|
|
237
238
|
): OpDefinition[] {
|
|
238
|
-
const { planner, brainIntelligence, vault, contextHealth } = runtime;
|
|
239
|
+
const { planner, brain, brainIntelligence, vault, contextHealth } = runtime;
|
|
239
240
|
const agentId = runtime.config.agentId;
|
|
240
241
|
|
|
241
242
|
return [
|
|
@@ -385,11 +386,149 @@ export function createOrchestrateOps(
|
|
|
385
386
|
planId: z.string().describe('ID of the plan to execute (flow planId or legacy planId)'),
|
|
386
387
|
domain: z.string().optional().describe('Domain for brain session tracking'),
|
|
387
388
|
context: z.string().optional().describe('Additional context for the brain session'),
|
|
389
|
+
runtime: z
|
|
390
|
+
.string()
|
|
391
|
+
.optional()
|
|
392
|
+
.describe(
|
|
393
|
+
'Runtime adapter type (e.g. "claude-code", "codex"). ' +
|
|
394
|
+
'When provided, dispatches via the adapter instead of the flow engine.',
|
|
395
|
+
),
|
|
396
|
+
subagent: z
|
|
397
|
+
.boolean()
|
|
398
|
+
.optional()
|
|
399
|
+
.describe(
|
|
400
|
+
'When true, dispatches plan tasks via SubagentDispatcher instead of FlowExecutor. ' +
|
|
401
|
+
'Each task runs as a separate subagent process.',
|
|
402
|
+
),
|
|
403
|
+
parallel: z
|
|
404
|
+
.boolean()
|
|
405
|
+
.optional()
|
|
406
|
+
.describe(
|
|
407
|
+
'Run subagent tasks in parallel (default: true). Only applies when subagent=true.',
|
|
408
|
+
),
|
|
409
|
+
maxConcurrent: z
|
|
410
|
+
.number()
|
|
411
|
+
.optional()
|
|
412
|
+
.describe('Max concurrent subagents (default: 3). Only applies when subagent=true.'),
|
|
388
413
|
}),
|
|
389
414
|
handler: async (params) => {
|
|
390
415
|
const planId = params.planId as string;
|
|
391
416
|
const domain = params.domain as string | undefined;
|
|
392
417
|
const context = params.context as string | undefined;
|
|
418
|
+
const runtimeType = params.runtime as string | undefined;
|
|
419
|
+
const useSubagent = params.subagent as boolean | undefined;
|
|
420
|
+
const parallelMode = params.parallel as boolean | undefined;
|
|
421
|
+
const maxConcurrentParam = params.maxConcurrent as number | undefined;
|
|
422
|
+
|
|
423
|
+
// ── Subagent dispatch path ───────────────────────────────────
|
|
424
|
+
// When subagent=true, dispatch plan tasks via SubagentDispatcher.
|
|
425
|
+
// Each task runs as a separate child process via the adapter layer.
|
|
426
|
+
if (useSubagent && runtime.subagentDispatcher) {
|
|
427
|
+
const entry = planStore.get(planId);
|
|
428
|
+
const legacyPlan = !entry ? planner.get(planId) : undefined;
|
|
429
|
+
const tasks =
|
|
430
|
+
entry?.plan.steps.map((s) => ({
|
|
431
|
+
taskId: s.id,
|
|
432
|
+
prompt: s.name,
|
|
433
|
+
workspace: process.cwd(),
|
|
434
|
+
runtime: runtimeType,
|
|
435
|
+
timeout: 300_000,
|
|
436
|
+
})) ??
|
|
437
|
+
legacyPlan?.tasks?.map((t) => ({
|
|
438
|
+
taskId: t.id,
|
|
439
|
+
prompt: t.title ?? t.description ?? '',
|
|
440
|
+
workspace: process.cwd(),
|
|
441
|
+
runtime: runtimeType,
|
|
442
|
+
timeout: 300_000,
|
|
443
|
+
})) ??
|
|
444
|
+
[];
|
|
445
|
+
|
|
446
|
+
const aggregated = await runtime.subagentDispatcher.dispatch(tasks, {
|
|
447
|
+
parallel: parallelMode ?? true,
|
|
448
|
+
maxConcurrent: maxConcurrentParam ?? 3,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Track in brain session
|
|
452
|
+
const existingSession = brainIntelligence.getSessionByPlanId(planId);
|
|
453
|
+
const session =
|
|
454
|
+
existingSession && !existingSession.endedAt
|
|
455
|
+
? existingSession
|
|
456
|
+
: brainIntelligence.lifecycle({
|
|
457
|
+
action: 'start',
|
|
458
|
+
domain,
|
|
459
|
+
context,
|
|
460
|
+
planId,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
contextHealth.track({
|
|
464
|
+
type: 'orchestrate_execute',
|
|
465
|
+
payloadSize: JSON.stringify(aggregated).length,
|
|
466
|
+
});
|
|
467
|
+
const healthStatus = contextHealth.check();
|
|
468
|
+
const healthWarning = buildHealthWarning(healthStatus, vault);
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
plan: { id: planId, status: 'executing' },
|
|
472
|
+
session,
|
|
473
|
+
subagent: {
|
|
474
|
+
status: aggregated.status,
|
|
475
|
+
totalTasks: aggregated.totalTasks,
|
|
476
|
+
completed: aggregated.completed,
|
|
477
|
+
failed: aggregated.failed,
|
|
478
|
+
durationMs: aggregated.durationMs,
|
|
479
|
+
totalUsage: aggregated.totalUsage,
|
|
480
|
+
},
|
|
481
|
+
...(healthWarning ? { contextHealth: healthWarning } : {}),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Adapter dispatch path ────────────────────────────────────
|
|
486
|
+
// When a runtime is specified, dispatch the plan's prompt via the
|
|
487
|
+
// adapter instead of the flow engine. This is the integration point
|
|
488
|
+
// for multi-runtime support (GH #410).
|
|
489
|
+
if (runtimeType && runtime.adapterRegistry) {
|
|
490
|
+
const adapter = runtime.adapterRegistry.get(runtimeType);
|
|
491
|
+
const entry = planStore.get(planId);
|
|
492
|
+
const prompt = entry?.plan.summary ?? `Execute plan ${planId}`;
|
|
493
|
+
|
|
494
|
+
const adapterResult = await adapter.execute({
|
|
495
|
+
runId: `${planId}-${Date.now()}`,
|
|
496
|
+
prompt,
|
|
497
|
+
workspace: process.cwd(),
|
|
498
|
+
config: { planId, domain },
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Track in brain session
|
|
502
|
+
const existingSession = brainIntelligence.getSessionByPlanId(planId);
|
|
503
|
+
const session =
|
|
504
|
+
existingSession && !existingSession.endedAt
|
|
505
|
+
? existingSession
|
|
506
|
+
: brainIntelligence.lifecycle({
|
|
507
|
+
action: 'start',
|
|
508
|
+
domain,
|
|
509
|
+
context,
|
|
510
|
+
planId,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
contextHealth.track({
|
|
514
|
+
type: 'orchestrate_execute',
|
|
515
|
+
payloadSize: JSON.stringify(adapterResult).length,
|
|
516
|
+
});
|
|
517
|
+
const healthStatus = contextHealth.check();
|
|
518
|
+
const healthWarning = buildHealthWarning(healthStatus, vault);
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
plan: { id: planId, status: 'executing' },
|
|
522
|
+
session,
|
|
523
|
+
adapter: {
|
|
524
|
+
type: runtimeType,
|
|
525
|
+
exitCode: adapterResult.exitCode,
|
|
526
|
+
summary: adapterResult.summary,
|
|
527
|
+
usage: adapterResult.usage,
|
|
528
|
+
},
|
|
529
|
+
...(healthWarning ? { contextHealth: healthWarning } : {}),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
393
532
|
|
|
394
533
|
// Look up flow plan
|
|
395
534
|
const entry = planStore.get(planId);
|
|
@@ -636,6 +775,19 @@ export function createOrchestrateOps(
|
|
|
636
775
|
filesModified,
|
|
637
776
|
});
|
|
638
777
|
|
|
778
|
+
// Record brain feedback for vault entries referenced in plan decisions
|
|
779
|
+
if (planObj && planObj.decisions) {
|
|
780
|
+
try {
|
|
781
|
+
recordPlanFeedback(
|
|
782
|
+
{ objective: planObj.objective, decisions: planObj.decisions },
|
|
783
|
+
brain,
|
|
784
|
+
brainIntelligence,
|
|
785
|
+
);
|
|
786
|
+
} catch {
|
|
787
|
+
// Brain feedback is best-effort
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
639
791
|
// Extract knowledge — runs regardless of plan existence
|
|
640
792
|
let extraction = null;
|
|
641
793
|
try {
|
package/src/runtime/runtime.ts
CHANGED
|
@@ -61,6 +61,9 @@ import { generatePersonaInstructions } from '../persona/prompt-generator.js';
|
|
|
61
61
|
import { OperatorProfileStore } from '../operator/operator-profile.js';
|
|
62
62
|
import { ContextHealthMonitor } from './context-health.js';
|
|
63
63
|
import { ShutdownRegistry } from './shutdown-registry.js';
|
|
64
|
+
import { RuntimeAdapterRegistry } from '../adapters/registry.js';
|
|
65
|
+
import { ClaudeCodeRuntimeAdapter } from '../adapters/claude-code-adapter.js';
|
|
66
|
+
import { SubagentDispatcher } from '../subagent/dispatcher.js';
|
|
64
67
|
|
|
65
68
|
/**
|
|
66
69
|
* Create a fully initialized agent runtime.
|
|
@@ -322,6 +325,8 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
322
325
|
shutdownRegistry.register('pipelineRunner', () => pipelineRunner.stop());
|
|
323
326
|
// Agency manager — close FSWatchers and debounce timers
|
|
324
327
|
shutdownRegistry.register('agencyManager', () => agencyManager.disable());
|
|
328
|
+
|
|
329
|
+
shutdownRegistry.register('subagentDispatcher', () => subagentDispatcher.cleanup());
|
|
325
330
|
// Loop manager — clear accumulated state
|
|
326
331
|
shutdownRegistry.register('loopManager', () => {
|
|
327
332
|
if (loop.isActive()) {
|
|
@@ -333,6 +338,14 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
333
338
|
}
|
|
334
339
|
});
|
|
335
340
|
|
|
341
|
+
// Runtime Adapter Registry — dispatch work to different AI CLIs
|
|
342
|
+
const adapterRegistry = new RuntimeAdapterRegistry();
|
|
343
|
+
adapterRegistry.register('claude-code', new ClaudeCodeRuntimeAdapter());
|
|
344
|
+
adapterRegistry.setDefault('claude-code');
|
|
345
|
+
|
|
346
|
+
// Subagent Dispatcher — spawn and manage child agent processes
|
|
347
|
+
const subagentDispatcher = new SubagentDispatcher({ adapterRegistry });
|
|
348
|
+
|
|
336
349
|
return {
|
|
337
350
|
config,
|
|
338
351
|
logger,
|
|
@@ -379,6 +392,8 @@ export function createAgentRuntime(config: AgentRuntimeConfig): AgentRuntime {
|
|
|
379
392
|
const p = loadPersona(agentId, config.persona ?? undefined);
|
|
380
393
|
return generatePersonaInstructions(p);
|
|
381
394
|
})(),
|
|
395
|
+
adapterRegistry,
|
|
396
|
+
subagentDispatcher,
|
|
382
397
|
contextHealth: new ContextHealthMonitor(),
|
|
383
398
|
shutdownRegistry,
|
|
384
399
|
createdAt: Date.now(),
|
|
@@ -17,6 +17,14 @@ function makeRuntime(overrides?: {
|
|
|
17
17
|
toolsUsed: string[];
|
|
18
18
|
filesModified: string[];
|
|
19
19
|
}>;
|
|
20
|
+
memories?: Array<{
|
|
21
|
+
id?: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
projectPath?: string;
|
|
24
|
+
summary?: string;
|
|
25
|
+
context?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
}>;
|
|
20
28
|
plans?: Array<{
|
|
21
29
|
id: string;
|
|
22
30
|
status: string;
|
|
@@ -45,6 +53,7 @@ function makeRuntime(overrides?: {
|
|
|
45
53
|
vault: {
|
|
46
54
|
stats: () => o.vaultStats ?? { totalEntries: 50, byType: { playbook: 5 } },
|
|
47
55
|
getRecent: (_n: number) => o.recentEntries ?? [],
|
|
56
|
+
listMemories: () => o.memories ?? [],
|
|
48
57
|
},
|
|
49
58
|
curator: {
|
|
50
59
|
healthAudit: () => ({
|
|
@@ -95,11 +104,34 @@ describe('session-briefing', () => {
|
|
|
95
104
|
expect(data.sections.find((s) => s.label === 'Welcome')).toBeUndefined();
|
|
96
105
|
});
|
|
97
106
|
|
|
98
|
-
it('includes Last session
|
|
107
|
+
it('includes Last session from cross-project memories when fresh', async () => {
|
|
108
|
+
const runtime = makeRuntime({
|
|
109
|
+
memories: [
|
|
110
|
+
{
|
|
111
|
+
id: 'mem-1',
|
|
112
|
+
createdAt: Date.now() - 600_000, // 10 min ago (ms)
|
|
113
|
+
projectPath: '/Users/me/projects/other-app',
|
|
114
|
+
summary: 'Fixed KPI card layout in the dashboard',
|
|
115
|
+
type: 'session',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
});
|
|
119
|
+
const ops = captureOps(createSessionBriefingOps(runtime));
|
|
120
|
+
const res = await executeOp(ops, 'session_briefing', {});
|
|
121
|
+
|
|
122
|
+
const data = res.data as { sections: Array<{ label: string; content: string }> };
|
|
123
|
+
const session = data.sections.find((s) => s.label === 'Last session');
|
|
124
|
+
expect(session).toBeDefined();
|
|
125
|
+
expect(session!.content).toContain('other-app');
|
|
126
|
+
expect(session!.content).toContain('Fixed KPI card layout');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('falls back to brain sessions when no fresh memories exist', async () => {
|
|
99
130
|
const runtime = makeRuntime({
|
|
131
|
+
memories: [], // no memories
|
|
100
132
|
sessions: [
|
|
101
133
|
{
|
|
102
|
-
endedAt: new Date(Date.now() -
|
|
134
|
+
endedAt: new Date(Date.now() - 3600_000).toISOString(), // 1h ago
|
|
103
135
|
domain: 'frontend',
|
|
104
136
|
context: 'Refactored button component',
|
|
105
137
|
toolsUsed: ['vault_search', 'brain_recommend'],
|
|
@@ -117,6 +149,63 @@ describe('session-briefing', () => {
|
|
|
117
149
|
expect(session!.content).toContain('Refactored button component');
|
|
118
150
|
});
|
|
119
151
|
|
|
152
|
+
it('skips Last session when all sessions are stale', async () => {
|
|
153
|
+
const staleTs = Date.now() - 72 * 3600_000; // 72h ago — beyond default 48h
|
|
154
|
+
const runtime = makeRuntime({
|
|
155
|
+
memories: [
|
|
156
|
+
{
|
|
157
|
+
id: 'mem-old',
|
|
158
|
+
createdAt: staleTs,
|
|
159
|
+
projectPath: '/old-project',
|
|
160
|
+
summary: 'Ancient session',
|
|
161
|
+
type: 'session',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
sessions: [
|
|
165
|
+
{
|
|
166
|
+
endedAt: new Date(staleTs).toISOString(),
|
|
167
|
+
domain: 'old',
|
|
168
|
+
context: 'Ancient brain session',
|
|
169
|
+
toolsUsed: [],
|
|
170
|
+
filesModified: [],
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
});
|
|
174
|
+
const ops = captureOps(createSessionBriefingOps(runtime));
|
|
175
|
+
const res = await executeOp(ops, 'session_briefing', {});
|
|
176
|
+
|
|
177
|
+
const data = res.data as { sections: Array<{ label: string; content: string }> };
|
|
178
|
+
const session = data.sections.find((s) => s.label === 'Last session');
|
|
179
|
+
expect(session).toBeUndefined();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('respects custom recencyHours parameter', async () => {
|
|
183
|
+
const runtime = makeRuntime({
|
|
184
|
+
memories: [
|
|
185
|
+
{
|
|
186
|
+
id: 'mem-3h',
|
|
187
|
+
createdAt: Date.now() - 3 * 3600_000, // 3h ago
|
|
188
|
+
projectPath: '/recent-project',
|
|
189
|
+
summary: 'Recent work',
|
|
190
|
+
type: 'session',
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
const ops = captureOps(createSessionBriefingOps(runtime));
|
|
195
|
+
|
|
196
|
+
// With 1h window — should skip
|
|
197
|
+
const narrow = await executeOp(ops, 'session_briefing', { recencyHours: 1 });
|
|
198
|
+
const narrowData = narrow.data as { sections: Array<{ label: string }> };
|
|
199
|
+
expect(narrowData.sections.find((s) => s.label === 'Last session')).toBeUndefined();
|
|
200
|
+
|
|
201
|
+
// With 4h window — should include
|
|
202
|
+
const wide = await executeOp(ops, 'session_briefing', { recencyHours: 4 });
|
|
203
|
+
const wideData = wide.data as { sections: Array<{ label: string; content: string }> };
|
|
204
|
+
const session = wideData.sections.find((s) => s.label === 'Last session');
|
|
205
|
+
expect(session).toBeDefined();
|
|
206
|
+
expect(session!.content).toContain('Recent work');
|
|
207
|
+
});
|
|
208
|
+
|
|
120
209
|
it('includes Active plans section', async () => {
|
|
121
210
|
const runtime = makeRuntime({
|
|
122
211
|
plans: [
|
|
@@ -234,6 +323,9 @@ describe('session-briefing', () => {
|
|
|
234
323
|
getRecent: () => {
|
|
235
324
|
throw new Error('no vault');
|
|
236
325
|
},
|
|
326
|
+
listMemories: () => {
|
|
327
|
+
throw new Error('no vault');
|
|
328
|
+
},
|
|
237
329
|
},
|
|
238
330
|
curator: {
|
|
239
331
|
healthAudit: () => {
|
|
@@ -57,22 +57,58 @@ export function createSessionBriefingOps(runtime: AgentRuntime): OpDefinition[]
|
|
|
57
57
|
// Vault stats unavailable — skip
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// 1. Last session
|
|
60
|
+
// 1. Last session — cross-project, with staleness threshold
|
|
61
61
|
try {
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
const recencyMs = (params.recencyHours as number) * 3600_000;
|
|
63
|
+
const cutoff = Date.now() - recencyMs;
|
|
64
|
+
|
|
65
|
+
// Try cross-project memories first (most recent work, any project)
|
|
66
|
+
const recentMemories = vault.listMemories({ type: 'session', limit: 3 });
|
|
67
|
+
const freshMemory = recentMemories.find((m) => {
|
|
68
|
+
const ts = m.createdAt > 1e12 ? m.createdAt : m.createdAt * 1000;
|
|
69
|
+
return ts > cutoff;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (freshMemory) {
|
|
73
|
+
const ts =
|
|
74
|
+
freshMemory.createdAt > 1e12 ? freshMemory.createdAt : freshMemory.createdAt * 1000;
|
|
75
|
+
const ago = formatTimeAgo(ts);
|
|
76
|
+
const project = freshMemory.projectPath
|
|
77
|
+
? ` [${freshMemory.projectPath.split('/').pop()}]`
|
|
78
|
+
: '';
|
|
79
|
+
const summary = freshMemory.summary
|
|
80
|
+
? `: ${freshMemory.summary.slice(0, 100)}`
|
|
81
|
+
: freshMemory.context
|
|
82
|
+
? `: ${freshMemory.context.slice(0, 100)}`
|
|
83
|
+
: '';
|
|
84
|
+
dataPoints += recentMemories.length;
|
|
72
85
|
sections.push({
|
|
73
86
|
label: 'Last session',
|
|
74
|
-
content: `(${ago})${
|
|
87
|
+
content: `(${ago})${project}${summary}`,
|
|
75
88
|
});
|
|
89
|
+
} else {
|
|
90
|
+
// Fall back to brain sessions (same-project only) if no fresh memory
|
|
91
|
+
const sessions = brainIntelligence.listSessions({ limit: 1, active: false });
|
|
92
|
+
dataPoints += sessions.length;
|
|
93
|
+
if (sessions.length > 0) {
|
|
94
|
+
const last = sessions[0];
|
|
95
|
+
const endTs = last.endedAt ? new Date(last.endedAt).getTime() : 0;
|
|
96
|
+
if (endTs > cutoff) {
|
|
97
|
+
const ago = formatTimeAgo(endTs);
|
|
98
|
+
const domain = last.domain ? ` [${last.domain}]` : '';
|
|
99
|
+
const context = last.context ? `: ${last.context.slice(0, 80)}` : '';
|
|
100
|
+
const tools =
|
|
101
|
+
last.toolsUsed.length > 0 ? `, used ${last.toolsUsed.length} tools` : '';
|
|
102
|
+
const files =
|
|
103
|
+
last.filesModified.length > 0
|
|
104
|
+
? `, modified ${last.filesModified.length} files`
|
|
105
|
+
: '';
|
|
106
|
+
sections.push({
|
|
107
|
+
label: 'Last session',
|
|
108
|
+
content: `(${ago})${domain}${context}${tools}${files}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
76
112
|
}
|
|
77
113
|
} catch {
|
|
78
114
|
// Session data unavailable — skip
|
package/src/runtime/types.ts
CHANGED
|
@@ -37,6 +37,8 @@ import type { OperatorProfileStore } from '../operator/operator-profile.js';
|
|
|
37
37
|
import type { OperatorContextStore } from '../operator/operator-context-store.js';
|
|
38
38
|
import type { ContextHealthMonitor } from './context-health.js';
|
|
39
39
|
import type { ShutdownRegistry } from './shutdown-registry.js';
|
|
40
|
+
import type { RuntimeAdapterRegistry } from '../adapters/registry.js';
|
|
41
|
+
import type { SubagentDispatcher } from '../subagent/dispatcher.js';
|
|
40
42
|
|
|
41
43
|
/**
|
|
42
44
|
* Configuration for creating an agent runtime.
|
|
@@ -129,6 +131,10 @@ export interface AgentRuntime {
|
|
|
129
131
|
persona: import('../persona/types.js').PersonaConfig;
|
|
130
132
|
/** Generated persona system instructions for LLM context. */
|
|
131
133
|
personaInstructions: import('../persona/types.js').PersonaSystemInstructions;
|
|
134
|
+
/** Runtime adapter registry — dispatch work to different AI CLIs. */
|
|
135
|
+
adapterRegistry: RuntimeAdapterRegistry;
|
|
136
|
+
/** Subagent dispatcher — spawn and manage child agent processes. */
|
|
137
|
+
subagentDispatcher: SubagentDispatcher;
|
|
132
138
|
/** Context health monitor — tracks tool call volume and context window fill. */
|
|
133
139
|
contextHealth: ContextHealthMonitor;
|
|
134
140
|
/** Shutdown registry — centralized cleanup for timers, watchers, child processes. */
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction Evaluator — checks session state against a compaction policy
|
|
3
|
+
* and determines whether the session should be rotated.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CompactionPolicy, CompactionResult, SessionState } from './compaction-policy.js';
|
|
7
|
+
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// DURATION PARSER
|
|
10
|
+
// =============================================================================
|
|
11
|
+
|
|
12
|
+
const DURATION_RE = /^(\d+)(ms|s|m|h|d)$/;
|
|
13
|
+
|
|
14
|
+
const MULTIPLIERS: Record<string, number> = {
|
|
15
|
+
ms: 1,
|
|
16
|
+
s: 1_000,
|
|
17
|
+
m: 60_000,
|
|
18
|
+
h: 3_600_000,
|
|
19
|
+
d: 86_400_000,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parse a simple duration string (e.g. '72h', '30m', '7d') into milliseconds.
|
|
24
|
+
* Returns `undefined` for invalid input.
|
|
25
|
+
*/
|
|
26
|
+
export function parseDuration(duration: string): number | undefined {
|
|
27
|
+
const match = DURATION_RE.exec(duration.trim());
|
|
28
|
+
if (!match) return undefined;
|
|
29
|
+
const [, value, unit] = match;
|
|
30
|
+
return Number(value) * MULTIPLIERS[unit];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// EVALUATOR
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Evaluate whether a session should be compacted based on policy thresholds.
|
|
39
|
+
*
|
|
40
|
+
* Returns the first triggered threshold as the reason. Checks in order:
|
|
41
|
+
* 1. maxRuns
|
|
42
|
+
* 2. maxInputTokens
|
|
43
|
+
* 3. maxAge
|
|
44
|
+
*/
|
|
45
|
+
export function shouldCompact(
|
|
46
|
+
session: SessionState,
|
|
47
|
+
policy: CompactionPolicy,
|
|
48
|
+
now: Date = new Date(),
|
|
49
|
+
): CompactionResult {
|
|
50
|
+
const noCompact: CompactionResult = { compact: false, reason: '', handoff: '' };
|
|
51
|
+
|
|
52
|
+
// Check maxRuns
|
|
53
|
+
if (policy.maxRuns !== undefined && session.runCount >= policy.maxRuns) {
|
|
54
|
+
return {
|
|
55
|
+
compact: true,
|
|
56
|
+
reason: `Run count (${session.runCount}) reached threshold (${policy.maxRuns})`,
|
|
57
|
+
handoff: '',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check maxInputTokens
|
|
62
|
+
if (policy.maxInputTokens !== undefined && session.inputTokens >= policy.maxInputTokens) {
|
|
63
|
+
return {
|
|
64
|
+
compact: true,
|
|
65
|
+
reason: `Input tokens (${session.inputTokens}) reached threshold (${policy.maxInputTokens})`,
|
|
66
|
+
handoff: '',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check maxAge
|
|
71
|
+
if (policy.maxAge !== undefined) {
|
|
72
|
+
const maxMs = parseDuration(policy.maxAge);
|
|
73
|
+
if (maxMs !== undefined) {
|
|
74
|
+
const startedAt = new Date(session.startedAt).getTime();
|
|
75
|
+
const elapsed = now.getTime() - startedAt;
|
|
76
|
+
if (elapsed >= maxMs) {
|
|
77
|
+
return {
|
|
78
|
+
compact: true,
|
|
79
|
+
reason: `Session age (${Math.round(elapsed / 60_000)}m) reached threshold (${policy.maxAge})`,
|
|
80
|
+
handoff: '',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return noCompact;
|
|
87
|
+
}
|