@triflux/remote 10.53.0 → 10.55.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.
Files changed (47) hide show
  1. package/hub/team/backend.mjs +10 -3
  2. package/hub/team/cli/commands/start/index.mjs +0 -2
  3. package/hub/team/handoff.mjs +1 -1
  4. package/hub/team/headless.mjs +41 -57
  5. package/hub/team/intervention.mjs +6 -4
  6. package/hub/team/nativeProxy.mjs +2 -48
  7. package/hub/team/pane.mjs +31 -18
  8. package/hub/team/psmux.mjs +36 -20
  9. package/hub/team/remote-session.mjs +66 -56
  10. package/hub/team/retry-state-machine.mjs +1 -1
  11. package/hub/team/session.mjs +80 -59
  12. package/hub/team/terminal-opener.mjs +5 -19
  13. package/hub/team/tui-viewer.mjs +2 -2
  14. package/hub/team/tui.mjs +0 -6
  15. package/hub/workers/factory.mjs +1 -10
  16. package/hub/workers/interface.mjs +0 -7
  17. package/package.json +2 -6
  18. package/scripts/lib/backup-rotation.mjs +60 -0
  19. package/scripts/lib/codex-profile-config.mjs +20 -7
  20. package/scripts/lib/codex-recovery.sh +2 -3
  21. package/scripts/lib/install-blob-history.json +3196 -0
  22. package/scripts/lib/install-retire.mjs +203 -0
  23. package/scripts/lib/legacy-hook-cleanup.mjs +148 -13
  24. package/scripts/lib/legacy-mcp-cleanup.mjs +405 -104
  25. package/scripts/lib/machine-profile.mjs +2 -2
  26. package/scripts/lib/mcp-filter.mjs +1 -42
  27. package/scripts/lib/mcp-guard-engine.mjs +198 -19
  28. package/scripts/lib/mcp-manifest.mjs +0 -40
  29. package/scripts/lib/process-utils.mjs +19 -0
  30. package/scripts/lib/stable-node.mjs +35 -0
  31. package/scripts/lib/test-env.mjs +20 -0
  32. package/cto/brief.mjs +0 -73
  33. package/cto/collect.mjs +0 -490
  34. package/cto/current.schema.json +0 -148
  35. package/cto/events.mjs +0 -208
  36. package/cto/hygiene.mjs +0 -320
  37. package/cto/lake-root.mjs +0 -127
  38. package/cto/status.mjs +0 -124
  39. package/hub/lib/cto-env.mjs +0 -1
  40. package/hub/team/notify.mjs +0 -385
  41. package/hub/team/process-cleanup.mjs +0 -386
  42. package/hub/team/routing.mjs +0 -249
  43. package/hub/team/staleState.mjs +0 -403
  44. package/hub/team/worker-signal.mjs +0 -263
  45. package/hub/team/worker-signal.types.d.ts +0 -52
  46. package/hub/workers/codex-mcp.mjs +0 -853
  47. package/scripts/lib/remote-spawn-transfer.mjs +0 -226
package/cto/collect.mjs DELETED
@@ -1,490 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import {
3
- existsSync,
4
- mkdirSync,
5
- readdirSync,
6
- readFileSync,
7
- statSync,
8
- } from "node:fs";
9
- import { join, relative } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
-
12
- import { renderBrief } from "./brief.mjs";
13
- import { appendCtoEvent } from "./events.mjs";
14
- import {
15
- readJsonLines,
16
- resolveLakeRootDir,
17
- writeAtomic,
18
- } from "./lake-root.mjs";
19
-
20
- const SCHEMA_VERSION = "cto-lake.v1";
21
- const SOURCE_REGISTRY = [
22
- {
23
- id: "git",
24
- kind: "repo",
25
- probe: "git rev-parse, git branch, git status",
26
- enabled: true,
27
- },
28
- {
29
- id: "ultragoal_omx",
30
- kind: "durable-goal-artifact",
31
- probe: ".omx/ultragoal/goals.json",
32
- enabled: true,
33
- },
34
- {
35
- id: "ultragoal_omc",
36
- kind: "durable-goal-artifact",
37
- probe: ".omc/ultragoal/goals.json",
38
- enabled: true,
39
- },
40
- {
41
- id: "handoffs",
42
- kind: "durable-handoff-artifact",
43
- probe: ".omx/handoffs/*",
44
- enabled: true,
45
- },
46
- ];
47
- const SOURCE_IDS = SOURCE_REGISTRY.map((source) => source.id);
48
-
49
- const SCHEMA_PATH = fileURLToPath(
50
- new URL("./current.schema.json", import.meta.url),
51
- );
52
-
53
- function isoNow(opts) {
54
- const value =
55
- opts.now instanceof Date
56
- ? opts.now
57
- : opts.now
58
- ? new Date(opts.now)
59
- : new Date();
60
- return value.toISOString();
61
- }
62
-
63
- function sourceState(available, status, detail, collectedAt) {
64
- return {
65
- available,
66
- status,
67
- detail,
68
- collected_at: collectedAt,
69
- };
70
- }
71
-
72
- function missingSource(status, detail, collectedAt) {
73
- return sourceState(false, status, detail, collectedAt);
74
- }
75
-
76
- function relPath(rootDir, filePath) {
77
- const rel = relative(rootDir, filePath).replace(/\\/g, "/");
78
- return rel && !rel.startsWith("..") ? rel : filePath;
79
- }
80
-
81
- function readJson(filePath) {
82
- return JSON.parse(readFileSync(filePath, "utf8"));
83
- }
84
-
85
- function listDirFiles(dirPath, rootDir, limit = 10) {
86
- if (!existsSync(dirPath)) return [];
87
- try {
88
- return readdirSync(dirPath, { withFileTypes: true })
89
- .filter((entry) => entry.isFile())
90
- .map((entry) => {
91
- const filePath = join(dirPath, entry.name);
92
- return {
93
- path: relPath(rootDir, filePath),
94
- mtime: statSync(filePath).mtime.toISOString(),
95
- };
96
- })
97
- .sort((a, b) => b.mtime.localeCompare(a.mtime))
98
- .slice(0, limit);
99
- } catch {
100
- return [];
101
- }
102
- }
103
-
104
- function execGit(args, rootDir, execFileSyncFn) {
105
- return execFileSyncFn("git", args, {
106
- cwd: rootDir,
107
- encoding: "utf8",
108
- stdio: ["ignore", "pipe", "pipe"],
109
- windowsHide: true,
110
- }).trim();
111
- }
112
-
113
- function collectGit(rootDir, collectedAt, execFileSyncFn) {
114
- try {
115
- const gitRoot = execGit(
116
- ["rev-parse", "--show-toplevel"],
117
- rootDir,
118
- execFileSyncFn,
119
- );
120
- const branch =
121
- execGit(["branch", "--show-current"], gitRoot, execFileSyncFn) || null;
122
- const head =
123
- execGit(["rev-parse", "--short=12", "HEAD"], gitRoot, execFileSyncFn) ||
124
- null;
125
- const porcelain = execGit(
126
- ["status", "--porcelain"],
127
- gitRoot,
128
- execFileSyncFn,
129
- );
130
- const dirty = porcelain.length > 0;
131
- const repo = { root: gitRoot, branch, head, dirty };
132
- return {
133
- repo,
134
- source: sourceState(
135
- true,
136
- dirty ? "dirty" : "clean",
137
- {
138
- branch,
139
- head,
140
- dirty,
141
- changed_entries: porcelain ? porcelain.split(/\r?\n/u).length : 0,
142
- },
143
- collectedAt,
144
- ),
145
- };
146
- } catch (error) {
147
- return {
148
- repo: { root: rootDir, branch: null, head: null, dirty: false },
149
- source: missingSource(
150
- "unavailable",
151
- `git status unavailable: ${error?.message || "unknown error"}`,
152
- collectedAt,
153
- ),
154
- };
155
- }
156
- }
157
-
158
- function normalizeGoals(parsed) {
159
- const rawGoals = Array.isArray(parsed?.goals)
160
- ? parsed.goals
161
- : Array.isArray(parsed)
162
- ? parsed
163
- : [];
164
- return rawGoals.map((goal) => ({
165
- id: goal?.id || null,
166
- title: goal?.title || goal?.objective || goal?.summary || null,
167
- status: goal?.status || "unknown",
168
- updated_at: goal?.updatedAt || goal?.updated_at || null,
169
- }));
170
- }
171
-
172
- function collectUltragoal(dirPath, rootDir, collectedAt) {
173
- const goalsPath = join(dirPath, "goals.json");
174
- const ledgerPath = join(dirPath, "ledger.jsonl");
175
- const briefPath = join(dirPath, "brief.md");
176
- if (
177
- !existsSync(goalsPath) &&
178
- !existsSync(ledgerPath) &&
179
- !existsSync(briefPath)
180
- ) {
181
- return missingSource(
182
- "no_shell_readable_artifact",
183
- "no ultragoal files found",
184
- collectedAt,
185
- );
186
- }
187
-
188
- const detail = {
189
- paths: {},
190
- goals_count: 0,
191
- active_goals: [],
192
- ledger_tail: [],
193
- brief: null,
194
- };
195
-
196
- try {
197
- if (existsSync(goalsPath)) {
198
- detail.paths.goals = relPath(rootDir, goalsPath);
199
- const parsed = readJson(goalsPath);
200
- const goals = normalizeGoals(parsed);
201
- detail.goals_count = goals.length;
202
- detail.active_goals = goals
203
- .filter(
204
- (goal) =>
205
- !["complete", "completed", "done"].includes(
206
- String(goal.status).toLowerCase(),
207
- ),
208
- )
209
- .slice(0, 5);
210
- }
211
- if (existsSync(ledgerPath)) {
212
- detail.paths.ledger = relPath(rootDir, ledgerPath);
213
- detail.ledger_tail = readJsonLines(ledgerPath, 5);
214
- }
215
- if (existsSync(briefPath)) {
216
- detail.paths.brief = relPath(rootDir, briefPath);
217
- detail.brief = readFileSync(briefPath, "utf8")
218
- .replace(/\s+/g, " ")
219
- .trim()
220
- .slice(0, 300);
221
- }
222
- return sourceState(true, "ok", detail, collectedAt);
223
- } catch (error) {
224
- return sourceState(
225
- true,
226
- "read_error",
227
- {
228
- path: relPath(rootDir, dirPath),
229
- error: error?.message || "ultragoal read failed",
230
- },
231
- collectedAt,
232
- );
233
- }
234
- }
235
-
236
- function collectHandoffs(rootDir, collectedAt) {
237
- const dirPath = join(rootDir, ".omx", "handoffs");
238
- const files = listDirFiles(dirPath, rootDir, 10);
239
- if (files.length === 0) {
240
- return missingSource(
241
- "no_shell_readable_artifact",
242
- "no .omx/handoffs files found",
243
- collectedAt,
244
- );
245
- }
246
- return sourceState(
247
- true,
248
- "ok",
249
- { path: relPath(rootDir, dirPath), files },
250
- collectedAt,
251
- );
252
- }
253
-
254
- function buildSummary(current) {
255
- const repo = current.repo;
256
- const omxGoals = current.sources.ultragoal_omx.detail?.active_goals || [];
257
- const omcGoals = current.sources.ultragoal_omc.detail?.active_goals || [];
258
- const availableSources = Object.values(current.sources).filter(
259
- (source) => source.available,
260
- ).length;
261
-
262
- return {
263
- repo_state: `branch ${repo.branch || "unknown"} at ${repo.head || "unknown"} is ${repo.dirty ? "dirty" : "clean"}`,
264
- active_goals: [...omxGoals, ...omcGoals].slice(0, 5),
265
- available_sources: availableSources,
266
- missing_sources: SOURCE_IDS.length - availableSources,
267
- };
268
- }
269
-
270
- function isPlainObject(value) {
271
- return value !== null && typeof value === "object" && !Array.isArray(value);
272
- }
273
-
274
- function assertPlainObject(name, value) {
275
- if (!isPlainObject(value)) throw new Error(`${name} must be object`);
276
- }
277
-
278
- function assertOnlyKeys(name, value, allowedKeys) {
279
- assertPlainObject(name, value);
280
- const allowed = new Set(allowedKeys);
281
- for (const key of Object.keys(value)) {
282
- if (!allowed.has(key)) throw new Error(`${name} has unexpected ${key}`);
283
- }
284
- }
285
-
286
- function assertString(name, value) {
287
- if (typeof value !== "string") throw new Error(`${name} must be string`);
288
- }
289
-
290
- function assertStringOrNull(name, value) {
291
- if (typeof value !== "string" && value !== null) {
292
- throw new Error(`${name} must be string or null`);
293
- }
294
- }
295
-
296
- function assertDateTimeStringOrNull(name, value) {
297
- assertStringOrNull(name, value);
298
- if (value !== null && Number.isNaN(Date.parse(value))) {
299
- throw new Error(`${name} must be date-time`);
300
- }
301
- }
302
-
303
- function assertBoolean(name, value) {
304
- if (typeof value !== "boolean") throw new Error(`${name} must be boolean`);
305
- }
306
-
307
- function validateLedgerEntry(entry, prefix = "ledger_tail entry") {
308
- assertOnlyKeys(prefix, entry, ["ts", "event", "source", "summary", "ref"]);
309
- assertDateTimeStringOrNull(`${prefix}.ts`, entry.ts);
310
- if (entry.ts === null) throw new Error(`${prefix}.ts must be string`);
311
- assertString(`${prefix}.event`, entry.event);
312
- assertString(`${prefix}.source`, entry.source);
313
- assertString(`${prefix}.summary`, entry.summary);
314
- if (
315
- entry.ref !== null &&
316
- typeof entry.ref !== "string" &&
317
- !isPlainObject(entry.ref)
318
- ) {
319
- throw new Error(`${prefix}.ref must be string, object, or null`);
320
- }
321
- }
322
-
323
- function isValidLedgerEntry(entry) {
324
- try {
325
- validateLedgerEntry(entry);
326
- return true;
327
- } catch {
328
- return false;
329
- }
330
- }
331
-
332
- function readLedgerTail(lakeRoot, limit = 5) {
333
- return readJsonLines(join(lakeRoot, "ledger.jsonl"), limit * 4)
334
- .filter(isValidLedgerEntry)
335
- .slice(-limit);
336
- }
337
-
338
- function validateSourceState(sourceId, source) {
339
- const prefix = `current.json source ${sourceId}`;
340
- assertOnlyKeys(prefix, source, [
341
- "available",
342
- "status",
343
- "detail",
344
- "collected_at",
345
- ]);
346
- assertBoolean(`${prefix}.available`, source.available);
347
- assertString(`${prefix}.status`, source.status);
348
- if (
349
- source.detail !== null &&
350
- typeof source.detail !== "string" &&
351
- typeof source.detail !== "object"
352
- ) {
353
- throw new Error(`${prefix}.detail must be string, object, array, or null`);
354
- }
355
- assertDateTimeStringOrNull(`${prefix}.collected_at`, source.collected_at);
356
- }
357
-
358
- function validateCurrent(current) {
359
- const schema = readJson(SCHEMA_PATH);
360
- assertOnlyKeys("current.json", current, schema.required);
361
- for (const field of schema.required) {
362
- if (!Object.hasOwn(current, field))
363
- throw new Error(`current.json missing ${field}`);
364
- }
365
- if (current.schema_version !== SCHEMA_VERSION) {
366
- throw new Error(`current.json schema_version must be ${SCHEMA_VERSION}`);
367
- }
368
- assertDateTimeStringOrNull("current.json.generated_at", current.generated_at);
369
- if (current.generated_at === null)
370
- throw new Error("current.json.generated_at must be string");
371
-
372
- assertOnlyKeys("current.json.repo", current.repo, [
373
- "root",
374
- "branch",
375
- "head",
376
- "dirty",
377
- ]);
378
- assertString("current.json.repo.root", current.repo.root);
379
- assertStringOrNull("current.json.repo.branch", current.repo.branch);
380
- assertStringOrNull("current.json.repo.head", current.repo.head);
381
- assertBoolean("current.json.repo.dirty", current.repo.dirty);
382
-
383
- assertOnlyKeys(
384
- "current.json.sources",
385
- current.sources,
386
- schema.properties.sources.required,
387
- );
388
- for (const sourceId of schema.properties.sources.required) {
389
- const source = current.sources?.[sourceId];
390
- if (!source) throw new Error(`current.json missing source ${sourceId}`);
391
- validateSourceState(sourceId, source);
392
- }
393
-
394
- assertPlainObject("current.json.summary", current.summary);
395
- if (!Array.isArray(current.ledger_tail))
396
- throw new Error("current.json ledger_tail must be array");
397
- for (const [index, entry] of current.ledger_tail.entries()) {
398
- validateLedgerEntry(entry, `ledger_tail[${index}]`);
399
- }
400
- }
401
-
402
- function collectSources(rootDir, collectedAt, execFileSyncFn) {
403
- const git = collectGit(rootDir, collectedAt, execFileSyncFn);
404
- const sources = {
405
- git: git.source,
406
- ultragoal_omx: collectUltragoal(
407
- join(rootDir, ".omx", "ultragoal"),
408
- rootDir,
409
- collectedAt,
410
- ),
411
- ultragoal_omc: collectUltragoal(
412
- join(rootDir, ".omc", "ultragoal"),
413
- rootDir,
414
- collectedAt,
415
- ),
416
- handoffs: collectHandoffs(rootDir, collectedAt),
417
- };
418
-
419
- return { repo: git.repo, sources };
420
- }
421
-
422
- function hasFlag(args, flag) {
423
- return Array.isArray(args) && args.includes(flag);
424
- }
425
-
426
- export async function runCollect(args = [], opts = {}) {
427
- if (hasFlag(args, "--help")) {
428
- (opts.stdout || process.stdout).write(
429
- "Usage: tfx cto collect [--json]\nRefresh the CTO lake from durable authority sources.\n",
430
- );
431
- return;
432
- }
433
- const rootDir = opts.rootDir || resolveLakeRootDir(process.cwd());
434
- const lakeRoot = opts.lakeRoot || join(rootDir, ".triflux", "lake");
435
- const stdout = opts.stdout || process.stdout;
436
- const stderr = opts.stderr || process.stderr;
437
- const execFileSyncFn = opts.execFileSyncFn || execFileSync;
438
- const generatedAt = isoNow(opts);
439
-
440
- mkdirSync(lakeRoot, { recursive: true });
441
-
442
- const { repo, sources } = collectSources(
443
- rootDir,
444
- generatedAt,
445
- execFileSyncFn,
446
- );
447
- const current = {
448
- schema_version: SCHEMA_VERSION,
449
- generated_at: generatedAt,
450
- repo,
451
- sources,
452
- summary: {},
453
- ledger_tail: readLedgerTail(lakeRoot),
454
- };
455
- current.summary = buildSummary(current);
456
-
457
- const event = {
458
- ts: generatedAt,
459
- event: "collect",
460
- source: "tfx_cto_collect",
461
- summary: `collected ${current.summary.available_sources}/${SOURCE_IDS.length} durable sources`,
462
- ref: {
463
- current_json: "current.json",
464
- current_md: "current.md",
465
- sources_json: "sources.json",
466
- },
467
- };
468
- const { appended } = await appendCtoEvent(lakeRoot, event, { stderr });
469
- if (appended) current.ledger_tail = readLedgerTail(lakeRoot);
470
-
471
- validateCurrent(current);
472
- writeAtomic(
473
- join(lakeRoot, "current.json"),
474
- `${JSON.stringify(current, null, 2)}\n`,
475
- );
476
- writeAtomic(join(lakeRoot, "current.md"), renderBrief(current));
477
- writeAtomic(
478
- join(lakeRoot, "sources.json"),
479
- `${JSON.stringify(SOURCE_REGISTRY, null, 2)}
480
- `,
481
- );
482
-
483
- if (opts.json === true || hasFlag(args, "--json")) {
484
- stdout.write(`${JSON.stringify(current, null, 2)}\n`);
485
- } else {
486
- stdout.write(`wrote ${join(lakeRoot, "current.json")} and current.md\n`);
487
- }
488
-
489
- return current;
490
- }
@@ -1,148 +0,0 @@
1
- {
2
- "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://triflux.local/schemas/cto/current.schema.json",
4
- "title": "Triflux CTO Lake Current Authority State",
5
- "description": "Contract for .triflux/lake/current.json. .triflux/lake is a triflux-owned, namespace-neutral cross-agent rollup for durable artifacts across Claude, Codex/OMX, OMC, and Triflux state. It reads and aggregates .omx/ultragoal and .omc/ultragoal execution ledgers without replacing or duplicating their authority. ledger.jsonl and sources.json companion contracts are documented in $defs.",
6
- "type": "object",
7
- "additionalProperties": false,
8
- "required": [
9
- "schema_version",
10
- "generated_at",
11
- "repo",
12
- "sources",
13
- "summary",
14
- "ledger_tail"
15
- ],
16
- "properties": {
17
- "schema_version": {
18
- "type": "string",
19
- "const": "cto-lake.v1"
20
- },
21
- "generated_at": {
22
- "type": "string",
23
- "format": "date-time"
24
- },
25
- "repo": {
26
- "type": "object",
27
- "additionalProperties": false,
28
- "required": ["root", "branch", "head", "dirty"],
29
- "properties": {
30
- "root": {
31
- "type": "string",
32
- "minLength": 1
33
- },
34
- "branch": {
35
- "type": ["string", "null"]
36
- },
37
- "head": {
38
- "type": ["string", "null"]
39
- },
40
- "dirty": {
41
- "type": "boolean"
42
- }
43
- }
44
- },
45
- "sources": {
46
- "type": "object",
47
- "description": "Durable artifact source states only. A source reports available:false when no shell-readable durable artifact exists.",
48
- "additionalProperties": false,
49
- "required": ["git", "ultragoal_omx", "ultragoal_omc", "handoffs"],
50
- "properties": {
51
- "git": {
52
- "$ref": "#/$defs/sourceState"
53
- },
54
- "ultragoal_omx": {
55
- "$ref": "#/$defs/sourceState"
56
- },
57
- "ultragoal_omc": {
58
- "$ref": "#/$defs/sourceState"
59
- },
60
- "handoffs": {
61
- "$ref": "#/$defs/sourceState"
62
- }
63
- }
64
- },
65
- "summary": {
66
- "type": "object",
67
- "description": "Human-oriented synthesized authority summary derived from durable artifacts. Phase 1 may add source-derived fields here without changing the top-level contract.",
68
- "additionalProperties": true
69
- },
70
- "ledger_tail": {
71
- "type": "array",
72
- "description": "Recent append-only .triflux/lake/ledger.jsonl entries copied into current.json for quick status rendering.",
73
- "items": {
74
- "$ref": "#/$defs/ledgerEntry"
75
- }
76
- }
77
- },
78
- "$defs": {
79
- "sourceState": {
80
- "description": "Every collected durable source reports { available, status, detail, collected_at }. available:false means the surface exposes no shell-readable artifact for this collection pass.",
81
- "type": "object",
82
- "additionalProperties": false,
83
- "required": ["available", "status", "detail", "collected_at"],
84
- "properties": {
85
- "available": {
86
- "type": "boolean"
87
- },
88
- "status": {
89
- "type": "string"
90
- },
91
- "detail": {
92
- "type": ["string", "object", "array", "null"]
93
- },
94
- "collected_at": {
95
- "type": ["string", "null"],
96
- "format": "date-time"
97
- }
98
- }
99
- },
100
- "ledgerEntry": {
101
- "description": ".triflux/lake/ledger.jsonl is append-only and single-writer. The collector must acquire .triflux/lake/ledger.jsonl.lock before appending so concurrent collectors cannot interleave or corrupt JSONL lines. Each line is one JSON object with this shape.",
102
- "type": "object",
103
- "additionalProperties": false,
104
- "required": ["ts", "event", "source", "summary", "ref"],
105
- "properties": {
106
- "ts": {
107
- "type": "string",
108
- "format": "date-time"
109
- },
110
- "event": {
111
- "type": "string"
112
- },
113
- "source": {
114
- "type": "string"
115
- },
116
- "summary": {
117
- "type": "string"
118
- },
119
- "ref": {
120
- "type": ["string", "object", "null"]
121
- }
122
- }
123
- },
124
- "sourceRegistry": {
125
- "$comment": ".triflux/lake/sources.json is an array of durable source probe registrations.",
126
- "type": "array",
127
- "items": {
128
- "type": "object",
129
- "additionalProperties": false,
130
- "required": ["id", "kind", "probe", "enabled"],
131
- "properties": {
132
- "id": {
133
- "type": "string"
134
- },
135
- "kind": {
136
- "type": "string"
137
- },
138
- "probe": {
139
- "type": ["string", "object"]
140
- },
141
- "enabled": {
142
- "type": "boolean"
143
- }
144
- }
145
- }
146
- }
147
- }
148
- }