Skip to content

Commit b168934

Browse files
authored
refactor: migrate agent schema and liveness utilities to TypeScript (#996)
## Summary - migrate quality-gate schema construction and schema normalization utilities from JavaScript to strict TypeScript - migrate context metrics and liveness polling with explicit runtime boundary types - preserve CommonJS entrypoints and verify compiled outputs remain in the npm package ## Validation - `npm run prepack` - `npm test` (2,901 passing; 18 pending) - `npm run typecheck` - targeted ESLint and Prettier checks - direct old/new behavior and export parity for every migrated module - liveness parity across 10 state-machine scenarios - `npx mocha tests/agent-stuck-recovery.test.js tests/isolated-task-recovery.test.js` (26 passing) - Opcore hypothetical, changed, Sense, graph update, introduced-change, and staged gates - npm package dry-run smoke (all four compiled runtime outputs present)
1 parent 83005a5 commit b168934

8 files changed

Lines changed: 537 additions & 322 deletions

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,8 +173,12 @@ src/omp-session-limits.js
173173
src/agent/agent-trigger-evaluator.js
174174
src/agent/agent-command-proofs-context.js
175175
src/agent/agent-quality-gates-context.js
176+
src/agent/agent-quality-gate-schema.js
176177
src/agent/rate-limit-backoff.js
177178
src/agent/guidance-queue.js
179+
src/agent/schema-utils.js
180+
src/agent/context-metrics.js
181+
src/agent/agent-liveness-poll.js
178182
src/agent/context-replay-policy.js
179183
src/agent/critical-agent-policy.js
180184
src/agent/provider-control-plane.js
Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,90 @@
1-
const { isPlatformSupported } = require('./agent-stuck-detector');
1+
interface AgentStuckDetectorModule {
2+
isPlatformSupported(): boolean;
3+
}
4+
5+
interface LivenessIsolation {
6+
enabled?: boolean;
7+
}
8+
9+
interface NestedExecutions {
10+
hasActive?: boolean;
11+
}
12+
13+
interface LivenessAgent {
14+
currentTask?: unknown;
15+
currentTaskId?: unknown;
16+
isolation?: LivenessIsolation | null;
17+
nestedExecutions?: NestedExecutions | null;
18+
livenessTerminationStarted?: boolean;
19+
livenessTerminationContext?: unknown;
20+
livenessTerminationRetryAt: number;
21+
taskStartedAt?: number | null;
22+
lastOutputTime?: number | null;
23+
consecutiveStaleWarnings: number;
24+
_publishLifecycle(event: string, details: Record<string, unknown>): unknown;
25+
}
26+
27+
interface TaskTiming {
28+
taskRuntime: number;
29+
timeSinceLastOutput: number;
30+
lastOutputTime: number;
31+
}
32+
33+
type AttemptTermination = (agent: LivenessAgent, settings: unknown) => unknown;
34+
35+
type BeginTermination = (
36+
agent: LivenessAgent,
37+
settings: unknown,
38+
reason: string,
39+
code: string,
40+
eventData: Record<string, unknown>
41+
) => unknown;
42+
43+
interface LivenessPollContext {
44+
agent: LivenessAgent;
45+
settings: unknown;
46+
configuredTimeout: number | null;
47+
staleDuration: number;
48+
warningsBeforeKill: number;
49+
attemptTermination: AttemptTermination;
50+
beginTermination: BeginTermination;
51+
}
52+
53+
function isAgentStuckDetectorModule(value: unknown): value is AgentStuckDetectorModule {
54+
return (
55+
typeof value === 'object' &&
56+
value !== null &&
57+
'isPlatformSupported' in value &&
58+
typeof value.isPlatformSupported === 'function'
59+
);
60+
}
61+
62+
const agentStuckDetectorModule: unknown = require('./agent-stuck-detector');
63+
if (!isAgentStuckDetectorModule(agentStuckDetectorModule)) {
64+
throw new TypeError('agent-stuck-detector must export isPlatformSupported');
65+
}
66+
const isPlatformSupported = agentStuckDetectorModule.isPlatformSupported;
267

3-
function hasRecoverableTask(agent) {
68+
function hasRecoverableTask(agent: LivenessAgent): boolean {
469
return (
570
Boolean(agent.currentTask) ||
671
Boolean(agent.isolation?.enabled && agent.currentTaskId) ||
772
agent.nestedExecutions?.hasActive === true
873
);
974
}
1075

11-
function handlePendingTermination(agent, settings, now, attemptTermination) {
76+
function handlePendingTermination(
77+
agent: LivenessAgent,
78+
settings: unknown,
79+
now: number,
80+
attemptTermination: AttemptTermination
81+
): boolean {
1282
if (!agent.livenessTerminationContext) return false;
1383
if (now >= agent.livenessTerminationRetryAt) attemptTermination(agent, settings);
1484
return true;
1585
}
1686

17-
function taskTiming(agent, now) {
87+
function taskTiming(agent: LivenessAgent, now: number): TaskTiming {
1888
const taskStartedAt = agent.taskStartedAt || agent.lastOutputTime || now;
1989
const lastOutputTime = agent.lastOutputTime || taskStartedAt;
2090
return {
@@ -24,7 +94,7 @@ function taskTiming(agent, now) {
2494
};
2595
}
2696

27-
function handleTaskTimeout(context, timing) {
97+
function handleTaskTimeout(context: LivenessPollContext, timing: TaskTiming): boolean {
2898
const { agent, settings, configuredTimeout, beginTermination } = context;
2999
if (!configuredTimeout || timing.taskRuntime < configuredTimeout) return false;
30100
beginTermination(
@@ -41,7 +111,7 @@ function handleTaskTimeout(context, timing) {
41111
return true;
42112
}
43113

44-
function publishStaleWarning(context, timing) {
114+
function publishStaleWarning(context: LivenessPollContext, timing: TaskTiming): void {
45115
const { agent, staleDuration, warningsBeforeKill } = context;
46116
agent.consecutiveStaleWarnings += 1;
47117
agent._publishLifecycle('AGENT_STALE_WARNING', {
@@ -56,7 +126,7 @@ function publishStaleWarning(context, timing) {
56126
});
57127
}
58128

59-
function terminateForInactivity(context, timing) {
129+
function terminateForInactivity(context: LivenessPollContext, timing: TaskTiming): void {
60130
const { agent, settings, staleDuration, beginTermination } = context;
61131
beginTermination(
62132
agent,
@@ -72,7 +142,7 @@ function terminateForInactivity(context, timing) {
72142
);
73143
}
74144

75-
function createLivenessPoll(context) {
145+
function createLivenessPoll(context: LivenessPollContext): () => void {
76146
const { agent, settings, staleDuration, warningsBeforeKill, attemptTermination } = context;
77147
return () => {
78148
if (!hasRecoverableTask(agent) || agent.livenessTerminationStarted) return;
@@ -90,4 +160,4 @@ function createLivenessPoll(context) {
90160
};
91161
}
92162

93-
module.exports = { createLivenessPoll };
163+
export = { createLivenessPoll };
Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,37 @@
1-
function buildQualityGateSchema() {
1+
type JsonSchemaType = 'array' | 'boolean' | 'integer' | 'number' | 'object' | 'string';
2+
3+
interface JsonSchema {
4+
type?: JsonSchemaType;
5+
description?: string;
6+
enum?: string[];
7+
items?: JsonSchema;
8+
properties?: Record<string, JsonSchema>;
9+
required?: string[];
10+
anyOf?: JsonSchema[];
11+
}
12+
13+
interface QualityGatePublishData {
14+
qualityGates?: unknown;
15+
[key: string]: unknown;
16+
}
17+
18+
interface AgentQualityGateConfig {
19+
role?: string;
20+
outputFormat?: string;
21+
requiredQualityGates?: unknown;
22+
jsonSchema?: JsonSchema;
23+
hooks?: {
24+
onComplete?: {
25+
config?: {
26+
content?: {
27+
data?: QualityGatePublishData;
28+
};
29+
};
30+
};
31+
};
32+
}
33+
34+
function buildQualityGateSchema(): JsonSchema {
235
return {
336
type: 'array',
437
description: 'Tool-neutral ship handoff quality gate evidence for configured required gates.',
@@ -59,17 +92,17 @@ function buildQualityGateSchema() {
5992
};
6093
}
6194

62-
function hasRequiredQualityGates(config) {
95+
function hasRequiredQualityGates(config: AgentQualityGateConfig): boolean {
6396
return Array.isArray(config.requiredQualityGates) && config.requiredQualityGates.length > 0;
6497
}
6598

66-
function shouldApplyValidatorQualityGateDefaults(config) {
99+
function shouldApplyValidatorQualityGateDefaults(config: AgentQualityGateConfig): boolean {
67100
return (
68101
config.role === 'validator' && config.outputFormat === 'json' && hasRequiredQualityGates(config)
69102
);
70103
}
71104

72-
function applyValidatorQualityGateSchema(config) {
105+
function applyValidatorQualityGateSchema(config: AgentQualityGateConfig): void {
73106
if (!shouldApplyValidatorQualityGateDefaults(config)) {
74107
return;
75108
}
@@ -80,7 +113,7 @@ function applyValidatorQualityGateSchema(config) {
80113
}
81114
}
82115

83-
function applyValidatorQualityGatePublishMapping(config) {
116+
function applyValidatorQualityGatePublishMapping(config: AgentQualityGateConfig): void {
84117
if (!shouldApplyValidatorQualityGateDefaults(config)) {
85118
return;
86119
}
@@ -91,12 +124,12 @@ function applyValidatorQualityGatePublishMapping(config) {
91124
}
92125
}
93126

94-
function applyValidatorQualityGateDefaults(config) {
127+
function applyValidatorQualityGateDefaults(config: AgentQualityGateConfig): void {
95128
applyValidatorQualityGateSchema(config);
96129
applyValidatorQualityGatePublishMapping(config);
97130
}
98131

99-
module.exports = {
132+
export = {
100133
applyValidatorQualityGateDefaults,
101134
buildQualityGateSchema,
102135
};

‎src/agent/context-metrics.js‎

Lines changed: 0 additions & 160 deletions
This file was deleted.

0 commit comments

Comments
 (0)