@bradheitmann/odin-sentinel 0.4.5 → 0.4.6

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 (57) hide show
  1. package/AGENTS.md +64 -0
  2. package/CLAUDE.md +43 -0
  3. package/README.md +102 -339
  4. package/dist/src/mcp/server.js +43 -12
  5. package/dist/src/mcp/server.js.map +1 -1
  6. package/dist/src/protocol/schemas.d.ts +2529 -4
  7. package/dist/src/protocol/schemas.js +214 -18
  8. package/dist/src/protocol/schemas.js.map +1 -1
  9. package/dist/src/protocol/service.d.ts +96 -2
  10. package/dist/src/protocol/service.js +516 -4
  11. package/dist/src/protocol/service.js.map +1 -1
  12. package/dist/src/protocol/surface-layout.d.ts +40 -1
  13. package/dist/src/protocol/surface-layout.js +98 -1
  14. package/dist/src/protocol/surface-layout.js.map +1 -1
  15. package/dist/src/protocol/validators.d.ts +3 -0
  16. package/dist/src/protocol/validators.js +28 -0
  17. package/dist/src/protocol/validators.js.map +1 -1
  18. package/dist/src/protocol/version.d.ts +3 -0
  19. package/dist/src/protocol/version.js +3 -0
  20. package/dist/src/protocol/version.js.map +1 -1
  21. package/dist/src/telemetry/config.d.ts +8 -0
  22. package/dist/src/telemetry/config.js +24 -0
  23. package/dist/src/telemetry/config.js.map +1 -1
  24. package/dist/src/telemetry/index.d.ts +5 -5
  25. package/dist/src/telemetry/index.js +3 -3
  26. package/dist/src/telemetry/index.js.map +1 -1
  27. package/dist/src/telemetry/redactor.js +25 -7
  28. package/dist/src/telemetry/redactor.js.map +1 -1
  29. package/dist/src/telemetry/report.d.ts +108 -0
  30. package/dist/src/telemetry/report.js +83 -3
  31. package/dist/src/telemetry/report.js.map +1 -1
  32. package/dist/src/telemetry/submit.d.ts +2 -0
  33. package/dist/src/telemetry/submit.js +79 -6
  34. package/dist/src/telemetry/submit.js.map +1 -1
  35. package/docs/guides/quick-start.md +112 -44
  36. package/docs/guides/quickstart-prompts.md +46 -113
  37. package/docs/guides/recommended-starter-team.md +45 -27
  38. package/docs/reference/client-compatibility.md +20 -43
  39. package/docs/reference/cost-and-privacy.md +26 -23
  40. package/docs/reference/distribution.md +40 -55
  41. package/docs/reference/public-surface-audit.md +35 -114
  42. package/package.json +19 -4
  43. package/protocol/SCP.md +8 -1
  44. package/protocol/bootstrap-skill.md +16 -11
  45. package/protocol/closeout.yaml +7 -1
  46. package/protocol/delegation.yaml +1 -1
  47. package/protocol/model-profiles.yaml +55 -1
  48. package/protocol/receipts/boot-receipt.yaml +42 -0
  49. package/protocol/receipts/team-manifest.yaml +41 -0
  50. package/protocol/roles.yaml +69 -1
  51. package/protocol/topology.yaml +78 -36
  52. package/scripts/audit/public-surface.mjs +47 -19
  53. package/scripts/audit/verify-pack.mjs +293 -27
  54. package/templates/dev-slice-template.md +56 -0
  55. package/templates/pm-role-template.md +61 -0
  56. package/templates/qa-slice-template.md +46 -0
  57. package/templates/team-manifest-template.yaml +163 -0
@@ -12,6 +12,109 @@ export type ModelSignal = {
12
12
  model: string;
13
13
  violations: number;
14
14
  };
15
+ export type DiagnosticStatus = "verified" | "missing" | "blocked" | "unknown" | "not_applicable";
16
+ export type DiagnosticCheck = {
17
+ status: DiagnosticStatus;
18
+ detail?: string;
19
+ evidence?: string;
20
+ };
21
+ export type PromptType = "full_protocol" | "short_boot_prompt" | "handoff" | "unknown";
22
+ export type ReadinessStatus = "ready" | "degraded" | "blocked" | "unknown";
23
+ export type ReceiptStatus = "emitted" | "missing" | "invalid" | "unknown" | "not_applicable";
24
+ export type WatchStatus = "active" | "passive" | "idle" | "blocked" | "unknown" | "not_applicable";
25
+ export type BlockerClassification = "none" | "stale_mcp_version" | "missing_skill" | "short_prompt" | "passive_odin" | "split_workspace" | "tab_heavy_layout" | "auth_blocker" | "permission_blocker" | "local_inference_stall" | "role_refusal" | "missing_protocol_text" | "operator_authority_confusion" | "receipt_missing" | "runbook_improvisation" | "readiness_gate_skipped";
26
+ export type RoleSlotLocator = {
27
+ workspace?: string;
28
+ pane?: string;
29
+ surface?: string;
30
+ };
31
+ export type RoleSlotDiagnostic = {
32
+ role: string;
33
+ team: string;
34
+ harness: string;
35
+ model: string;
36
+ locator: RoleSlotLocator;
37
+ mcpAccess: DiagnosticCheck;
38
+ mcpVersion?: string;
39
+ minimumCompatibleMcpVersion?: string;
40
+ skillAccess: DiagnosticCheck;
41
+ fullProtocolTextReceived: DiagnosticCheck;
42
+ promptType: PromptType;
43
+ operatorAuthorityRecognized: DiagnosticCheck;
44
+ readinessStatus: ReadinessStatus;
45
+ bootReceiptStatus: ReceiptStatus;
46
+ activeWatchStatus: WatchStatus;
47
+ blockerClassification: BlockerClassification[];
48
+ childContextText?: string;
49
+ notes?: string[];
50
+ };
51
+ export type HarnessFailureDiagnostics = {
52
+ openHandsAuthApiCredentials: DiagnosticCheck;
53
+ kiloCodeLogin: DiagnosticCheck;
54
+ gooseLocalInferenceStall: DiagnosticCheck;
55
+ crushPermissionPrompt: DiagnosticCheck;
56
+ piRoleRefusal: DiagnosticCheck;
57
+ staleMcpVersion: DiagnosticCheck;
58
+ };
59
+ export type LayoutDiagnostics = {
60
+ sameWorkspace: boolean | "unknown";
61
+ splitWorkspace: boolean;
62
+ spatialPodLayout: boolean | "unknown";
63
+ tabHeavyLayout: boolean;
64
+ cmuxAvailable: boolean | "unknown";
65
+ degradedLayoutWarnings: string[];
66
+ };
67
+ export type LaunchRunbookDiagnostics = {
68
+ deterministicRunbookFollowed: boolean | "unknown";
69
+ firstImprovisationPoint?: string;
70
+ missingPrompts: string[];
71
+ missingTools: string[];
72
+ missingSchemas: string[];
73
+ };
74
+ export type ReadinessGateDiagnostics = {
75
+ childMcpVerified: DiagnosticCheck;
76
+ skillVerified: DiagnosticCheck;
77
+ authVerified: DiagnosticCheck;
78
+ firstRunPromptVerified: DiagnosticCheck;
79
+ modelSmokeVerified: DiagnosticCheck;
80
+ roleCompatibilityVerified: DiagnosticCheck;
81
+ };
82
+ export type ReleaseVersionDiagnostics = {
83
+ currentServerVersion: string;
84
+ minimumCompatibleVersion: string;
85
+ childReportedMcpVersions: Array<{
86
+ role: string;
87
+ version: string;
88
+ }>;
89
+ driftWarnings: string[];
90
+ };
91
+ export type ArtifactDiagnostics = {
92
+ planningArtifactsLocalOnly: boolean | "unknown";
93
+ publicTemplatesSanitized: boolean | "unknown";
94
+ privateArtifactWarnings: string[];
95
+ };
96
+ export type ChildContextWindow = {
97
+ role: string;
98
+ text: string;
99
+ };
100
+ export type AxDiagnostics = {
101
+ roleSlots: RoleSlotDiagnostic[];
102
+ childAgentQuestions: string[];
103
+ odinQuestions: string[];
104
+ harnessFailures: HarnessFailureDiagnostics;
105
+ layout: LayoutDiagnostics;
106
+ launchRunbook: LaunchRunbookDiagnostics;
107
+ readinessGates: ReadinessGateDiagnostics;
108
+ versions: ReleaseVersionDiagnostics;
109
+ artifacts: ArtifactDiagnostics;
110
+ pastedChildContextWindows?: ChildContextWindow[];
111
+ };
112
+ export type AxSummary = {
113
+ roleSlotCount: number;
114
+ blockerClassifications: BlockerClassification[];
115
+ degradedLayout: boolean;
116
+ driftWarningCount: number;
117
+ };
15
118
  export type SessionReportInput = {
16
119
  teamCount: number;
17
120
  violations: ViolationEntry[];
@@ -20,11 +123,16 @@ export type SessionReportInput = {
20
123
  peakContextPct: number;
21
124
  closeoutClean: boolean;
22
125
  modelSignals: ModelSignal[];
126
+ ax?: Partial<AxDiagnostics>;
23
127
  };
24
128
  export type SessionReport = SessionReportInput & {
25
129
  version: string;
26
130
  compiledAt: string;
27
131
  violationCount: number;
28
132
  haltCount: number;
133
+ axSummary?: AxSummary;
29
134
  };
135
+ export declare const CHILD_AGENT_DIAGNOSTIC_QUESTIONS: readonly ["What role slot, team, harness, model, workspace, pane, and surface were you assigned?", "Did you have ODIN Sentinel MCP access, and what MCP version did you see?", "Did you have native skill access for the requested role, or was a required skill missing?", "Did you receive the full protocol text, a handoff, or only a short boot prompt?", "Did you recognize A/EXEC-PM operator authority and your own implementation/QA limits?", "Did you emit the required boot receipt or explain why receipt emission was blocked?", "What blocker or confusion stopped progress, using the closest standard classification?", "What exact prompt, tool, schema, layout, auth, or runbook change would make launch smoother next time?"];
136
+ export declare const ODIN_DIAGNOSTIC_QUESTIONS: readonly ["Were you explicitly instructed to poll actively, or did you interpret the role as passive observation?", "Which workspaces, panes, surfaces, agents, files, queues, or receipts were you watching?", "Did plan mode, read-only mode, permission prompts, or role boundaries conflict with active intervention?", "What exact polling prompt or cadence would have made the watch loop unambiguous?", "Which terminal states did you detect: idle, blocked, still thinking, input-bar-only, delivered-no-ack, or done?", "What intervention would you have issued if active polling authority had been clear?"];
137
+ export declare function getAxDiagnosticQuestions(scope?: "child" | "odin" | "all"): string[];
30
138
  export declare function compileSessionReport(input: SessionReportInput, version: string): SessionReport;
@@ -1,10 +1,90 @@
1
+ import { redactPayload } from "./redactor.js";
2
+ export const CHILD_AGENT_DIAGNOSTIC_QUESTIONS = [
3
+ "What role slot, team, harness, model, workspace, pane, and surface were you assigned?",
4
+ "Did you have ODIN Sentinel MCP access, and what MCP version did you see?",
5
+ "Did you have native skill access for the requested role, or was a required skill missing?",
6
+ "Did you receive the full protocol text, a handoff, or only a short boot prompt?",
7
+ "Did you recognize A/EXEC-PM operator authority and your own implementation/QA limits?",
8
+ "Did you emit the required boot receipt or explain why receipt emission was blocked?",
9
+ "What blocker or confusion stopped progress, using the closest standard classification?",
10
+ "What exact prompt, tool, schema, layout, auth, or runbook change would make launch smoother next time?"
11
+ ];
12
+ export const ODIN_DIAGNOSTIC_QUESTIONS = [
13
+ "Were you explicitly instructed to poll actively, or did you interpret the role as passive observation?",
14
+ "Which workspaces, panes, surfaces, agents, files, queues, or receipts were you watching?",
15
+ "Did plan mode, read-only mode, permission prompts, or role boundaries conflict with active intervention?",
16
+ "What exact polling prompt or cadence would have made the watch loop unambiguous?",
17
+ "Which terminal states did you detect: idle, blocked, still thinking, input-bar-only, delivered-no-ack, or done?",
18
+ "What intervention would you have issued if active polling authority had been clear?"
19
+ ];
20
+ const HARNESS_BLOCKER_MAP = [
21
+ ["openHandsAuthApiCredentials", "auth_blocker"],
22
+ ["kiloCodeLogin", "auth_blocker"],
23
+ ["gooseLocalInferenceStall", "local_inference_stall"],
24
+ ["crushPermissionPrompt", "permission_blocker"],
25
+ ["piRoleRefusal", "role_refusal"],
26
+ ["staleMcpVersion", "stale_mcp_version"]
27
+ ];
28
+ function uniq(values) {
29
+ return [...new Set(values)];
30
+ }
31
+ function isBlocking(status) {
32
+ return status === "missing" || status === "blocked";
33
+ }
34
+ function summarizeAx(ax) {
35
+ if (!ax)
36
+ return undefined;
37
+ const blockers = [];
38
+ for (const roleSlot of ax.roleSlots ?? []) {
39
+ blockers.push(...roleSlot.blockerClassification.filter((entry) => entry !== "none"));
40
+ if (roleSlot.promptType === "short_boot_prompt")
41
+ blockers.push("short_prompt");
42
+ if (roleSlot.fullProtocolTextReceived.status === "missing")
43
+ blockers.push("missing_protocol_text");
44
+ if (roleSlot.skillAccess.status === "missing")
45
+ blockers.push("missing_skill");
46
+ if (roleSlot.activeWatchStatus === "passive" || roleSlot.activeWatchStatus === "idle")
47
+ blockers.push("passive_odin");
48
+ if (roleSlot.bootReceiptStatus === "missing")
49
+ blockers.push("receipt_missing");
50
+ }
51
+ if (ax.harnessFailures) {
52
+ for (const [key, classification] of HARNESS_BLOCKER_MAP) {
53
+ if (isBlocking(ax.harnessFailures[key].status))
54
+ blockers.push(classification);
55
+ }
56
+ }
57
+ if (ax.layout?.splitWorkspace)
58
+ blockers.push("split_workspace");
59
+ if (ax.layout?.tabHeavyLayout)
60
+ blockers.push("tab_heavy_layout");
61
+ if (ax.launchRunbook?.deterministicRunbookFollowed === false)
62
+ blockers.push("runbook_improvisation");
63
+ if ((ax.versions?.driftWarnings.length ?? 0) > 0)
64
+ blockers.push("stale_mcp_version");
65
+ return {
66
+ roleSlotCount: ax.roleSlots?.length ?? 0,
67
+ blockerClassifications: uniq(blockers),
68
+ degradedLayout: Boolean(ax.layout?.splitWorkspace || ax.layout?.tabHeavyLayout || (ax.layout?.degradedLayoutWarnings.length ?? 0) > 0),
69
+ driftWarningCount: ax.versions?.driftWarnings.length ?? 0
70
+ };
71
+ }
72
+ export function getAxDiagnosticQuestions(scope = "all") {
73
+ if (scope === "child")
74
+ return [...CHILD_AGENT_DIAGNOSTIC_QUESTIONS];
75
+ if (scope === "odin")
76
+ return [...ODIN_DIAGNOSTIC_QUESTIONS];
77
+ return [...CHILD_AGENT_DIAGNOSTIC_QUESTIONS, ...ODIN_DIAGNOSTIC_QUESTIONS];
78
+ }
1
79
  export function compileSessionReport(input, version) {
80
+ const redacted = redactPayload(input);
2
81
  return {
3
- ...input,
82
+ ...redacted,
4
83
  version,
5
84
  compiledAt: new Date().toISOString(),
6
- violationCount: input.violations.length,
7
- haltCount: input.halts.length
85
+ violationCount: redacted.violations.length,
86
+ haltCount: redacted.halts.length,
87
+ axSummary: summarizeAx(redacted.ax)
8
88
  };
9
89
  }
10
90
  //# sourceMappingURL=report.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/telemetry/report.ts"],"names":[],"mappings":"AAkCA,MAAM,UAAU,oBAAoB,CAAC,KAAyB,EAAE,OAAe;IAC7E,OAAO;QACL,GAAG,KAAK;QACR,OAAO;QACP,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,cAAc,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM;QACvC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;KAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../../src/telemetry/report.ts"],"names":[],"mappings":"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"}
@@ -10,5 +10,7 @@ export type SubmitOptions = {
10
10
  config?: TelemetryConfig;
11
11
  fetchImpl?: typeof fetch;
12
12
  timeoutMs?: number;
13
+ userConsentConfirmed?: boolean;
13
14
  };
15
+ export declare function toTelemetryPayload(report: Record<string, unknown>): Record<string, unknown>;
14
16
  export declare function submitSessionReport(report: Record<string, unknown>, options?: SubmitOptions): Promise<SubmitResult>;
@@ -1,7 +1,80 @@
1
- import { readTelemetryConfig } from "./config.js";
2
- import { redactPayload } from "./redactor.js";
1
+ import { readTelemetryConfig, redactTelemetryEndpoint } from "./config.js";
2
+ import { redactPayload, redactString } from "./redactor.js";
3
3
  const DEFAULT_TIMEOUT_MS = 8000;
4
+ function isRecord(value) {
5
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
6
+ }
7
+ function safeString(value, maxLen = 96) {
8
+ if (typeof value !== "string")
9
+ return "";
10
+ const redacted = redactString(value).trim();
11
+ return redacted.length > maxLen ? redacted.slice(0, maxLen) : redacted;
12
+ }
13
+ function safeInt(value, fallback, min, max) {
14
+ if (typeof value !== "number" || !Number.isFinite(value))
15
+ return fallback;
16
+ const integer = Math.trunc(value);
17
+ if (integer < min)
18
+ return min;
19
+ if (integer > max)
20
+ return max;
21
+ return integer;
22
+ }
23
+ function safeStringArray(value) {
24
+ if (!Array.isArray(value))
25
+ return [];
26
+ return [...new Set(value.map((entry) => safeString(entry)).filter(Boolean))];
27
+ }
28
+ function violationClasses(report) {
29
+ const direct = safeStringArray(report.violationClasses);
30
+ if (direct.length > 0)
31
+ return direct;
32
+ if (!Array.isArray(report.violations))
33
+ return [];
34
+ return [...new Set(report.violations.flatMap((entry) => isRecord(entry) ? [safeString(entry.class)] : []).filter(Boolean))];
35
+ }
36
+ function blockerClassifications(report) {
37
+ const axSummary = isRecord(report.axSummary) ? report.axSummary : {};
38
+ return safeStringArray(axSummary.blockerClassifications);
39
+ }
40
+ function modelSignals(report) {
41
+ if (!Array.isArray(report.modelSignals))
42
+ return [];
43
+ return report.modelSignals.flatMap((entry) => {
44
+ if (!isRecord(entry))
45
+ return [];
46
+ return [{
47
+ role: safeString(entry.role),
48
+ model: safeString(entry.model),
49
+ violations: safeInt(entry.violations, 0, 0, 10_000)
50
+ }];
51
+ });
52
+ }
53
+ export function toTelemetryPayload(report) {
54
+ const axSummary = isRecord(report.axSummary) ? report.axSummary : {};
55
+ return {
56
+ version: safeString(report.version, 32),
57
+ teamCount: safeInt(report.teamCount, 0, 0, 26),
58
+ violationCount: safeInt(report.violationCount, Array.isArray(report.violations) ? report.violations.length : 0, 0, 10_000),
59
+ haltCount: safeInt(report.haltCount, Array.isArray(report.halts) ? report.halts.length : 0, 0, 10_000),
60
+ layoutDriftEvents: safeInt(report.layoutDriftEvents, 0, 0, 10_000),
61
+ peakContextPct: safeInt(report.peakContextPct, 0, 0, 100),
62
+ closeoutClean: report.closeoutClean === true,
63
+ modelSignals: modelSignals(report),
64
+ violationClasses: violationClasses(report),
65
+ blockerClassifications: blockerClassifications(report),
66
+ roleSlotCount: safeInt(axSummary.roleSlotCount, 0, 0, 10_000),
67
+ driftWarningCount: safeInt(axSummary.driftWarningCount, 0, 0, 10_000),
68
+ degradedLayout: axSummary.degradedLayout === true
69
+ };
70
+ }
4
71
  export async function submitSessionReport(report, options = {}) {
72
+ if (options.userConsentConfirmed !== true) {
73
+ return {
74
+ submitted: false,
75
+ reason: "explicit user consent required for telemetry submission"
76
+ };
77
+ }
5
78
  const config = options.config ?? readTelemetryConfig();
6
79
  if (!config.enabled || !config.endpoint) {
7
80
  return {
@@ -9,7 +82,7 @@ export async function submitSessionReport(report, options = {}) {
9
82
  reason: "telemetry not configured (set ODIN_TELEMETRY_ENDPOINT to opt in)"
10
83
  };
11
84
  }
12
- const redacted = redactPayload(report);
85
+ const redacted = redactPayload(toTelemetryPayload(report));
13
86
  const fetchImpl = options.fetchImpl ?? fetch;
14
87
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
15
88
  const controller = new AbortController();
@@ -30,7 +103,7 @@ export async function submitSessionReport(report, options = {}) {
30
103
  }
31
104
  return {
32
105
  submitted: response.ok,
33
- endpoint: config.endpoint,
106
+ endpoint: redactTelemetryEndpoint(config.endpoint),
34
107
  status: response.status,
35
108
  id: typeof parsed.id === "string" ? parsed.id : undefined,
36
109
  reason: response.ok ? undefined : `HTTP ${response.status}`
@@ -39,8 +112,8 @@ export async function submitSessionReport(report, options = {}) {
39
112
  catch (error) {
40
113
  return {
41
114
  submitted: false,
42
- endpoint: config.endpoint,
43
- reason: error instanceof Error ? error.message : "network error"
115
+ endpoint: redactTelemetryEndpoint(config.endpoint),
116
+ reason: error instanceof Error ? redactString(error.message) : "network error"
44
117
  };
45
118
  }
46
119
  finally {
@@ -1 +1 @@
1
- {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../../src/telemetry/submit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAwB,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAgB9C,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAEhC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA+B,EAC/B,UAAyB,EAAE;IAE3B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,mBAAmB,EAAE,CAAC;IACvD,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxC,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,kEAAkE;SAC3E,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAE1D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAE9D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE;YAChD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,IAAI,MAAM,GAAmC,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmC,CAAC;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;QACxE,CAAC;QAED,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,EAAE;YACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,EAAE,EAAE,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;YACzD,MAAM,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,QAAQ,CAAC,MAAM,EAAE;SAC5D,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;SACjE,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"submit.js","sourceRoot":"","sources":["../../../src/telemetry/submit.ts"],"names":[],"mappings":"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"}
@@ -1,74 +1,142 @@
1
1
  # Quick Start
2
2
 
3
- ODIN Sentinel runs as a local MCP server over stdio.
3
+ ODIN Sentinel runs as a local MCP server over stdio. It returns protocol
4
+ resources, startup packets, validation results, delegation envelopes, closeout
5
+ checklists, and optional telemetry redaction helpers.
4
6
 
5
- It does not provide model inference. It does not host a backend. Your MCP client
6
- starts the process, calls ODIN tools, and reads ODIN protocol resources.
7
+ Plain version: install the package, connect it to an agent app, run one smoke
8
+ test, then check that each selected agent can sign in, use its model, and follow
9
+ the role instructions. If any step blocks, stop there and choose the safest next
10
+ step. You are still in control.
7
11
 
8
- ## From Source
12
+ ## Which Path Should I Choose?
13
+
14
+ - New to this: use the global install path, then run the global smoke test.
15
+ - Avoid global installs: use the zero-install `npx` command everywhere.
16
+ - Using Claude Code plugin support: use the plugin README, then come back here
17
+ for readiness checks.
18
+ - Local model users: run the normal smoke test and a separate local inference
19
+ smoke test before launching agents.
20
+
21
+ ## 1. Prerequisites
22
+
23
+ - Node.js `>=22.13.0`
24
+ - npm or pnpm
25
+ - at least one MCP-capable harness
26
+ - CMUX for governed team mode
27
+ - account/auth readiness for each selected harness
28
+ - local model endpoint readiness if you plan to use local inference
29
+
30
+ ## 2. Install
9
31
 
10
32
  ```bash
11
- pnpm install
12
- pnpm run build
13
- node dist/src/bin/index.js
33
+ npm i -g @bradheitmann/odin-sentinel
14
34
  ```
15
35
 
16
- ## Client Configuration
17
-
18
- Use the built server as a stdio command:
36
+ Or use zero-install from MCP configs:
19
37
 
20
- ```json
21
- {
22
- "mcpServers": {
23
- "odin-sentinel": {
24
- "command": "node",
25
- "args": ["/absolute/path/to/odin-sentinel/dist/src/bin/index.js"]
26
- }
27
- }
28
- }
38
+ ```bash
39
+ npx -y -p @bradheitmann/odin-sentinel odin-sentinel-mcp
29
40
  ```
30
41
 
31
- For local development:
42
+ ## 3. Configure MCP
43
+
44
+ Point each selected harness at the `odin-sentinel-mcp` stdio command. Restart the
45
+ harness after editing config.
32
46
 
33
47
  ```json
34
48
  {
35
49
  "mcpServers": {
36
50
  "odin-sentinel": {
37
- "command": "pnpm",
38
- "args": ["exec", "tsx", "/absolute/path/to/odin-sentinel/src/bin/index.ts"]
51
+ "command": "npx",
52
+ "args": ["-y", "-p", "@bradheitmann/odin-sentinel", "odin-sentinel-mcp"]
39
53
  }
40
54
  }
41
55
  }
42
56
  ```
43
57
 
44
- ## First Calls
45
-
46
- Ask the client to call:
58
+ ## 4. Install Skill Or Fallback Context
47
59
 
48
- 1. `odin.get_version`
49
- 2. `odin.get_runtime_notice`
50
- 3. `odin.get_startup_packet`
60
+ Use the best available context path for each harness:
51
61
 
52
- Then read:
62
+ 1. Native skill, if the harness supports it.
63
+ 2. Plugin/install path that bundles the skill and MCP server, if available.
64
+ 3. Full prompt injection from `odin.get_bootstrap_skill`, if skill/plugin support
65
+ is unavailable.
66
+ 4. Protocol snapshot from `odin.export_protocol_snapshot` for non-MCP or bridge
67
+ clients.
53
68
 
54
- 1. `odin://protocol/main`
55
- 2. `odin://protocol/roles`
56
- 3. `odin://protocol/topology`
57
- 4. `odin://protocol/delegation`
69
+ MCP supplies tools/resources. Native skill context improves automatic invocation.
70
+ Prompt injection is fallback only.
58
71
 
59
- That is enough for a client to understand the default team shape and how to
60
- start cleanly.
72
+ ## 5. Smoke Test
61
73
 
62
- For a suggested starter team, see
63
- [recommended-starter-team.md](recommended-starter-team.md).
74
+ Global install smoke test:
64
75
 
65
- ## Local State
76
+ ```bash
77
+ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}\n' \
78
+ | odin-sentinel-mcp
79
+ ```
66
80
 
67
- ODIN may point agents at these project-local locations:
81
+ Zero-install smoke test:
68
82
 
69
- - `docs/handoffs/`
70
- - `.odin/handoffs/`
71
- - `.odin/audit/`
83
+ ```bash
84
+ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}\n' \
85
+ | npx -y -p @bradheitmann/odin-sentinel odin-sentinel-mcp
86
+ ```
72
87
 
73
- They are not bundled state. Create them in the project where your agents are
74
- working if you want persistent handoffs or audit notes.
88
+ Expect `serverInfo.name = odin-sentinel` and `serverInfo.version = 0.4.6`.
89
+ Minimum compatible child MCP version: `0.4.5`.
90
+
91
+ ## 6. Auth And Provider Readiness
92
+
93
+ Ask whether selected harnesses are already provisioned. Do not ask users to paste
94
+ API keys into prompts or docs. Verify provider status without printing secret
95
+ values. Common secret/provider paths include Doppler, 1Password CLI, environment
96
+ variables, direnv, mise, and dotenv-style files.
97
+
98
+ If those names are unfamiliar, use this safer question instead: "Is this agent
99
+ already signed in or configured outside this chat?" If the answer is no, pause
100
+ launch and either sign in through the tool's normal UI, ask someone to configure
101
+ the provider outside chat, choose another harness, or keep the role slot empty.
102
+
103
+ For local inference, endpoint reachability is not enough. The selected model must
104
+ return visible content within the timeout and must not return only
105
+ `reasoning_content`.
106
+
107
+ ## 7. CMUX Governed-Team Launch
108
+
109
+ Governed team mode requires CMUX. Keep EXEC PM in the same CMUX workspace as the
110
+ rest of the team by default. Use spatial/pod organization, not tab-only layouts,
111
+ for the canonical operator surface.
112
+
113
+ Slot provisioning may happen before readiness is complete. Do not launch role
114
+ occupants until readiness passes or EXEC PM records a waiver/substitution.
115
+
116
+ ## 8. Deterministic Bootstrap Order
117
+
118
+ 1. Confirm Node.js and package version.
119
+ 2. Configure and smoke-test ODIN MCP.
120
+ 3. Install native skill or prepare full prompt fallback.
121
+ 4. Open CMUX and compute the target layout.
122
+ 5. Select harnesses and check auth/account readiness.
123
+ 6. Smoke-test local inference if used.
124
+ 7. Run role-compatibility smoke tests.
125
+ 8. Launch EXEC PM in the CMUX workspace.
126
+ 9. Stage role slots and collect boot receipts.
127
+ 10. Activate work only after the manifest validates.
128
+
129
+ ## Troubleshooting
130
+
131
+ | Symptom | Action |
132
+ | --- | --- |
133
+ | MCP unavailable | Restart the host, re-check config path, and run the stdio smoke test. |
134
+ | Skill missing | Install the native skill where supported or use `odin.get_bootstrap_skill` as full prompt fallback. |
135
+ | Auth/login required | Ask the user to provision the provider; verify status without printing secrets. |
136
+ | Permission prompt | Stop and route to PM/user; do not bypass silently. |
137
+ | Stale MCP version | Reinstall with `@latest`, restart host, and confirm `0.4.6`. |
138
+ | Local inference stall | Require visible content within timeout; endpoint-only success is insufficient. |
139
+ | Role refusal | Mark the role `NON_GOVERNED_ONE_SHOT_ONLY` unless MCP/skill/full protocol proof is established. |
140
+
141
+ `NON_GOVERNED_ONE_SHOT_ONLY` means the agent may help with one bounded task, but
142
+ is not suitable for a persistent governed role.