sneakoscope 7.3.0 → 7.5.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/README.md +117 -5
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/codex-app-command.js +1 -1
- package/dist/cli/command-manifest-lite.js +2 -1
- package/dist/cli/install-helpers-codex-lb-selftest.js +2 -1
- package/dist/cli/install-helpers-install-support.js +6 -16
- package/dist/cli/install-helpers.js +59 -50
- package/dist/cli/router.js +9 -1
- package/dist/config/skills-manifest.json +50 -71
- package/dist/core/codex-app.js +5 -5
- package/dist/core/codex-native/core-skill-manifest.js +9 -6
- package/dist/core/commands/image-ux-review-command.js +243 -50
- package/dist/core/commands/naruto-command.js +245 -8
- package/dist/core/commands/ppt-command.js +2 -2
- package/dist/core/commands/telegram-command.js +123 -10
- package/dist/core/commands/triwiki-command.js +11 -1
- package/dist/core/commands/triwiki-graph-command.js +160 -0
- package/dist/core/commands/wiki-command.js +100 -20
- package/dist/core/errors/message.js +3 -0
- package/dist/core/feature-registry.js +2 -2
- package/dist/core/fsx.js +7 -3
- package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
- package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
- package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
- package/dist/core/hooks-runtime.js +28 -86
- package/dist/core/image-ux-review/callout-extraction.js +6 -0
- package/dist/core/image-ux-review/imagegen-adapter.js +54 -18
- package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
- package/dist/core/image-ux-review.js +182 -7
- package/dist/core/imagegen/codex-lb-imagegen-target.js +22 -7
- package/dist/core/imagegen/imagegen-capability.js +11 -4
- package/dist/core/imagegen/require-imagegen.js +29 -5
- package/dist/core/init/skills.js +22 -10
- package/dist/core/install/installed-package-smoke.js +157 -4
- package/dist/core/managed-path-safety.js +8 -10
- package/dist/core/mcp-config/backup.js +2 -4
- package/dist/core/mcp-config/config-reader.js +2 -4
- package/dist/core/mcp-config/scope.js +3 -5
- package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
- package/dist/core/naruto/context-graph-advisor.js +0 -0
- package/dist/core/pipeline-internals/runtime-core.js +44 -0
- package/dist/core/policy/latest-version-guidance.js +113 -0
- package/dist/core/ppt.js +8 -8
- package/dist/core/release/gate-manifest.js +32 -1
- package/dist/core/release/npm-stage-contract.js +21 -0
- package/dist/core/release/package-size-budget.js +2 -2
- package/dist/core/release/release-gate-contract.js +5 -0
- package/dist/core/release/stage-publish.js +27 -0
- package/dist/core/responses-stream.js +65 -19
- package/dist/core/routes/design-policy.js +7 -2
- package/dist/core/routes/dollar-manifest-lite.js +0 -4
- package/dist/core/routes/evidence.js +1 -1
- package/dist/core/routes.js +12 -8
- package/dist/core/safety/command-contract/contracts.js +2 -0
- package/dist/core/safety/command-contract/types.js +1 -1
- package/dist/core/search/context-graph-seeds.js +205 -0
- package/dist/core/search/context.js +0 -0
- package/dist/core/search/provider.js +2 -2
- package/dist/core/subagents/naruto-help-contract.js +2 -1
- package/dist/core/subagents/naruto-host-credentials.js +161 -0
- package/dist/core/subagents/official-subagent-preparation.js +4 -1
- package/dist/core/subagents/official-subagent-prompt.js +44 -2
- package/dist/core/subagents/official-subagent-runner.js +26 -8
- package/dist/core/subagents/triwiki-attention.js +135 -123
- package/dist/core/telegram/bot-api.js +59 -4
- package/dist/core/telegram/config.js +3 -2
- package/dist/core/telegram/hub.js +3 -2
- package/dist/core/telegram/owner-lock.js +4 -6
- package/dist/core/telegram/setup.js +223 -73
- package/dist/core/triwiki/code-pack.js +12 -120
- package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
- package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
- package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
- package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
- package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
- package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
- package/dist/core/triwiki/context-graph/contracts.js +236 -0
- package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
- package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
- package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
- package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
- package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
- package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
- package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
- package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
- package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
- package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
- package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
- package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
- package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
- package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
- package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
- package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
- package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
- package/dist/core/triwiki/context-graph/graph-index.js +143 -0
- package/dist/core/triwiki/context-graph/ids.js +0 -0
- package/dist/core/triwiki/context-graph/lint/index.js +0 -0
- package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
- package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
- package/dist/core/triwiki/context-graph/paths.js +116 -0
- package/dist/core/triwiki/context-graph/profiles.js +78 -0
- package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
- package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
- package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
- package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
- package/dist/core/triwiki/context-graph/projections/index.js +7 -0
- package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
- package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
- package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
- package/dist/core/triwiki/context-graph/query/explain.js +0 -0
- package/dist/core/triwiki/context-graph/query/index.js +178 -0
- package/dist/core/triwiki/context-graph/query/load.js +116 -0
- package/dist/core/triwiki/context-graph/query/pack.js +117 -0
- package/dist/core/triwiki/context-graph/query/rank.js +142 -0
- package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
- package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
- package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
- package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
- package/dist/core/triwiki/context-graph/query-types.js +29 -0
- package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
- package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
- package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
- package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
- package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
- package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
- package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
- package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
- package/dist/core/verification/context-graph-affected.js +271 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/context-graph-check.js +191 -0
- package/dist/scripts/helper-dedup-check.js +0 -1
- package/dist/scripts/installed-package-smoke-check.js +4 -1
- package/dist/scripts/latest-version-guidance-check.js +74 -0
- package/dist/scripts/release-metadata-check.js +20 -3
- package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
- package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
- package/package.json +5 -3
- package/release-gates.v2.json +174 -0
- package/schemas/triwiki/context-graph.schema.json +119 -0
- package/dist/core/triwiki/code-index-scanner.js +0 -309
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
import { ui as cliUi } from '../../cli/cli-theme.js';
|
|
3
4
|
import { createMission, findLatestMission, getOrCreateExplicitNarutoMission, getOrCreateSessionMission, loadStateForSession, loadMission, sessionStateKey, setCurrent, updateCurrentIfMissionAndRun } from '../mission.js';
|
|
4
5
|
import { closeWorkOrderLedgerForRouteResult, createAndWriteWorkOrderLedgerForPrompt } from '../work-order-ledger.js';
|
|
5
6
|
import { appendJsonl, exists, nowIso, readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
|
|
6
|
-
import { SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME, bindTrustworthySubagentParentSummaryToRun, persistOrReuseTrustworthySubagentParentSummary, readSubagentEvents, writeSubagentEvidence } from '../subagents/subagent-evidence.js';
|
|
7
|
+
import { SUBAGENT_EVENT_LOG_FILENAME, SUBAGENT_EVIDENCE_FILENAME, SUBAGENT_PARENT_SUMMARY_FILENAME, bindTrustworthySubagentParentSummaryToRun, normalizeSubagentParentSummary, persistOrReuseTrustworthySubagentParentSummary, readSubagentEvents, writeSubagentEvidence } from '../subagents/subagent-evidence.js';
|
|
7
8
|
import { buildNarutoHelpResult } from '../subagents/naruto-help-contract.js';
|
|
9
|
+
import { resolveNarutoCredentialPolicy } from '../subagents/naruto-host-credentials.js';
|
|
10
|
+
import { DEFAULT_SUBAGENT_EFFORT, DEFAULT_SUBAGENT_MODEL, NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from '../subagents/model-policy.js';
|
|
8
11
|
import { buildNarutoProofProjection } from '../subagents/naruto-proof-projection.js';
|
|
9
12
|
import { withFileLock } from '../locks/file-lock.js';
|
|
10
13
|
import { codexAppSessionKey, detectCodexAppSession, runOfficialSubagentWorkflow } from '../subagents/official-subagent-runner.js';
|
|
@@ -14,7 +17,9 @@ import { effectiveSubagentTarget, refreshSubagentWaveLifecycle } from '../subage
|
|
|
14
17
|
import { HOST_CAPABILITY_HOOK_RUNTIME_FILENAME, bindParentSummaryToHostCapabilityEvidence, createHostCapabilityHookRuntimeBinding, resolveHostCapabilityHookRuntimeBinding } from '../agent-bridge/host-capability-runtime.js';
|
|
15
18
|
import { renderHostCapabilityBlockedLines } from '../agent-bridge/host-capability-policy.js';
|
|
16
19
|
import { uniqueStrings } from '../text/strings.js';
|
|
20
|
+
import { completeNarutoTerminalBundle, refreshOfficialSubagentCompletionArtifacts } from '../hooks-runtime/official-subagent-lifecycle.js';
|
|
17
21
|
export { buildNarutoGateResult } from '../subagents/official-subagent-preparation.js';
|
|
22
|
+
const MAX_PARENT_SUMMARY_STDIN_BYTES = 1024 * 1024;
|
|
18
23
|
let nextNarutoPreparationFailureInjectionForTest = null;
|
|
19
24
|
export function injectNextNarutoPreparationFailureForTest(value) {
|
|
20
25
|
nextNarutoPreparationFailureInjectionForTest = value;
|
|
@@ -24,6 +29,23 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
24
29
|
if (args.some((arg) => arg === '--glm' || arg.startsWith('--glm=')))
|
|
25
30
|
return blockGlmOverride(args.includes('--json'));
|
|
26
31
|
const parsed = parseNarutoArgs(args);
|
|
32
|
+
const credentialPolicy = parsed.credentialPolicy;
|
|
33
|
+
if (credentialPolicy.blockers.length) {
|
|
34
|
+
const blocked = {
|
|
35
|
+
schema: 'sks.naruto-credential-policy.v1',
|
|
36
|
+
ok: false,
|
|
37
|
+
blockers: credentialPolicy.blockers,
|
|
38
|
+
hint: 'sks naruto run --auth-mode=host --model-provider=<config.toml provider block> --provider-env-key=<ENV NAME>'
|
|
39
|
+
};
|
|
40
|
+
if (args.includes('--json'))
|
|
41
|
+
console.log(JSON.stringify(blocked, null, 2));
|
|
42
|
+
else {
|
|
43
|
+
console.error(`Naruto credential policy blocked: ${credentialPolicy.blockers.join(', ')}`);
|
|
44
|
+
console.error(blocked.hint);
|
|
45
|
+
}
|
|
46
|
+
process.exitCode = 2;
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
27
49
|
if (parsed.argumentErrors.length) {
|
|
28
50
|
const result = argumentBlock(parsed.argumentErrors);
|
|
29
51
|
return emit(parsed, result, () => {
|
|
@@ -41,8 +63,185 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
|
|
|
41
63
|
return narutoSubagents(parsed);
|
|
42
64
|
if (parsed.action === 'proof')
|
|
43
65
|
return narutoProof(parsed);
|
|
66
|
+
if (parsed.action === 'parent-summary')
|
|
67
|
+
return narutoParentSummary(parsed);
|
|
44
68
|
return narutoRun(parsed);
|
|
45
69
|
}
|
|
70
|
+
async function narutoParentSummary(parsed) {
|
|
71
|
+
const root = await sksRoot();
|
|
72
|
+
const appSession = detectCodexAppSession();
|
|
73
|
+
const sessionKey = appSession ? codexAppSessionKey() : null;
|
|
74
|
+
if (!appSession || !sessionKey) {
|
|
75
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_app_session_required']);
|
|
76
|
+
}
|
|
77
|
+
return withFileLock({
|
|
78
|
+
lockPath: path.join(root, '.sneakoscope', 'state', `naruto-session-${sessionStateKey(sessionKey)}.lock`),
|
|
79
|
+
timeoutMs: 20_000,
|
|
80
|
+
staleMs: 120_000
|
|
81
|
+
}, () => narutoParentSummaryTransaction(parsed, root, sessionKey));
|
|
82
|
+
}
|
|
83
|
+
async function narutoParentSummaryTransaction(parsed, root, sessionKey) {
|
|
84
|
+
const state = await loadStateForSession(root, sessionKey).catch(() => null);
|
|
85
|
+
const route = String(state?.route || state?.route_command || state?.mode || '')
|
|
86
|
+
.replace(/^\$/, '')
|
|
87
|
+
.replace(/[-_]/g, '')
|
|
88
|
+
.toUpperCase();
|
|
89
|
+
const stateBlockers = uniqueStrings([
|
|
90
|
+
...(state?._session_key === sessionStateKey(sessionKey) ? [] : ['naruto_parent_summary_session_state_mismatch']),
|
|
91
|
+
...(state?.session_scope === sessionKey ? [] : ['naruto_parent_summary_session_scope_mismatch']),
|
|
92
|
+
...(state?.mission_id === parsed.missionId ? [] : ['naruto_parent_summary_active_mission_mismatch']),
|
|
93
|
+
...(route === 'NARUTO' ? [] : ['naruto_parent_summary_active_route_mismatch']),
|
|
94
|
+
...(state?.route_closed === true ? ['naruto_parent_summary_route_closed'] : [])
|
|
95
|
+
]);
|
|
96
|
+
if (stateBlockers.length > 0)
|
|
97
|
+
return blockedParentSummary(parsed, stateBlockers);
|
|
98
|
+
const loaded = await loadMission(root, parsed.missionId).catch(() => null);
|
|
99
|
+
if (!loaded)
|
|
100
|
+
return blockedParentSummary(parsed, [`naruto_parent_summary_mission_not_found:${parsed.missionId}`]);
|
|
101
|
+
const plan = await readJson(path.join(loaded.dir, SUBAGENT_PLAN_FILENAME), null).catch(() => null);
|
|
102
|
+
const workflowRunId = String(plan?.workflow_run_id || '').trim();
|
|
103
|
+
const planBlockers = uniqueStrings([
|
|
104
|
+
...(plan?.schema === 'sks.subagent-plan.v1' ? [] : ['naruto_parent_summary_plan_schema_invalid']),
|
|
105
|
+
...(plan?.workflow === 'official_codex_subagent' ? [] : ['naruto_parent_summary_plan_workflow_invalid']),
|
|
106
|
+
...(plan?.mission_id === parsed.missionId ? [] : ['naruto_parent_summary_plan_mission_mismatch']),
|
|
107
|
+
...(plan?.session_scope === sessionKey ? [] : ['naruto_parent_summary_plan_session_scope_mismatch']),
|
|
108
|
+
...(workflowRunId ? [] : ['naruto_parent_summary_plan_run_id_missing']),
|
|
109
|
+
...(workflowRunId && state?.official_subagent_run_id === workflowRunId
|
|
110
|
+
? []
|
|
111
|
+
: ['naruto_parent_summary_active_run_mismatch'])
|
|
112
|
+
]);
|
|
113
|
+
if (planBlockers.length > 0)
|
|
114
|
+
return blockedParentSummary(parsed, planBlockers);
|
|
115
|
+
const stdin = await readBoundedParentSummaryStdin();
|
|
116
|
+
if (!stdin.ok)
|
|
117
|
+
return blockedParentSummary(parsed, [stdin.blocker]);
|
|
118
|
+
const raw = stdin.value;
|
|
119
|
+
let submitted;
|
|
120
|
+
try {
|
|
121
|
+
submitted = JSON.parse(raw);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_stdin_json_invalid']);
|
|
125
|
+
}
|
|
126
|
+
if (!submitted || typeof submitted !== 'object' || Array.isArray(submitted)) {
|
|
127
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_stdin_object_required']);
|
|
128
|
+
}
|
|
129
|
+
const normalized = normalizeSubagentParentSummary(submitted);
|
|
130
|
+
const summaryBlockers = uniqueStrings([
|
|
131
|
+
...(normalized.trustworthy && normalized.raw ? [] : normalized.blockers.length
|
|
132
|
+
? normalized.blockers.map((blocker) => `naruto_parent_summary_schema:${blocker}`)
|
|
133
|
+
: ['naruto_parent_summary_schema_invalid']),
|
|
134
|
+
...(!normalized.run_id
|
|
135
|
+
? ['naruto_parent_summary_run_id_missing']
|
|
136
|
+
: normalized.run_id === workflowRunId
|
|
137
|
+
? []
|
|
138
|
+
: ['naruto_parent_summary_run_id_mismatch'])
|
|
139
|
+
]);
|
|
140
|
+
if (summaryBlockers.length > 0)
|
|
141
|
+
return blockedParentSummary(parsed, summaryBlockers);
|
|
142
|
+
const submittedSummary = normalized.raw;
|
|
143
|
+
if (!submittedSummary)
|
|
144
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_schema_invalid']);
|
|
145
|
+
const existing = await readJson(path.join(loaded.dir, SUBAGENT_PARENT_SUMMARY_FILENAME), null).catch(() => null);
|
|
146
|
+
if (existing !== null) {
|
|
147
|
+
const normalizedExisting = normalizeSubagentParentSummary(existing);
|
|
148
|
+
const [existingGate, existingSummary, existingEvidence] = await Promise.all([
|
|
149
|
+
readJson(path.join(loaded.dir, NARUTO_GATE_FILENAME), null).catch(() => null),
|
|
150
|
+
readJson(path.join(loaded.dir, NARUTO_SUMMARY_FILENAME), null).catch(() => null),
|
|
151
|
+
readJson(path.join(loaded.dir, SUBAGENT_EVIDENCE_FILENAME), null).catch(() => null)
|
|
152
|
+
]);
|
|
153
|
+
const terminal = completeNarutoTerminalBundle({
|
|
154
|
+
workflowRunId,
|
|
155
|
+
gate: existingGate,
|
|
156
|
+
summary: existingSummary,
|
|
157
|
+
evidence: existingEvidence,
|
|
158
|
+
parentSummary: existing
|
|
159
|
+
});
|
|
160
|
+
if (terminal) {
|
|
161
|
+
if (!normalizedExisting.trustworthy || !normalizedExisting.raw) {
|
|
162
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_existing_canonical_invalid']);
|
|
163
|
+
}
|
|
164
|
+
if (normalizedExisting.run_id !== workflowRunId) {
|
|
165
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_existing_run_id_mismatch']);
|
|
166
|
+
}
|
|
167
|
+
if (!sameParentAuthoredSummary(normalizedExisting.raw, submittedSummary)) {
|
|
168
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_conflicts_with_canonical']);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const evidence = await refreshOfficialSubagentCompletionArtifacts(root, state, submittedSummary, sessionKey);
|
|
173
|
+
if (!evidence)
|
|
174
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_lifecycle_commit_rejected']);
|
|
175
|
+
const [persisted, summary, gate] = await Promise.all([
|
|
176
|
+
readJson(path.join(loaded.dir, SUBAGENT_PARENT_SUMMARY_FILENAME), null).catch(() => null),
|
|
177
|
+
readJson(path.join(loaded.dir, NARUTO_SUMMARY_FILENAME), null).catch(() => null),
|
|
178
|
+
readJson(path.join(loaded.dir, NARUTO_GATE_FILENAME), null).catch(() => null)
|
|
179
|
+
]);
|
|
180
|
+
const normalizedPersisted = normalizeSubagentParentSummary(persisted);
|
|
181
|
+
if (!normalizedPersisted.trustworthy
|
|
182
|
+
|| normalizedPersisted.run_id !== workflowRunId
|
|
183
|
+
|| !sameParentAuthoredSummary(normalizedPersisted.raw, submittedSummary)) {
|
|
184
|
+
return blockedParentSummary(parsed, ['naruto_parent_summary_canonical_commit_mismatch']);
|
|
185
|
+
}
|
|
186
|
+
const hardBlocked = normalizedPersisted.status === 'failed';
|
|
187
|
+
const accepted = gate?.passed === true || hardBlocked;
|
|
188
|
+
const result = {
|
|
189
|
+
schema: NARUTO_RESULT_SCHEMA,
|
|
190
|
+
action: 'parent-summary',
|
|
191
|
+
ok: gate?.passed === true,
|
|
192
|
+
accepted,
|
|
193
|
+
status: summary?.status || evidence?.status || normalizedPersisted.status || 'incomplete',
|
|
194
|
+
mission_id: parsed.missionId,
|
|
195
|
+
workflow_run_id: workflowRunId,
|
|
196
|
+
parent_summary_status: normalizedPersisted.status,
|
|
197
|
+
completion_evidence: gate?.passed === true,
|
|
198
|
+
blockers: Array.isArray(gate?.blockers) ? gate.blockers : [],
|
|
199
|
+
artifacts: narutoArtifactLinks(evidence)
|
|
200
|
+
};
|
|
201
|
+
return emit(parsed, result, () => renderParentSummaryResult(result), result.ok !== true);
|
|
202
|
+
}
|
|
203
|
+
async function readBoundedParentSummaryStdin() {
|
|
204
|
+
let value = '';
|
|
205
|
+
let bytes = 0;
|
|
206
|
+
process.stdin.setEncoding('utf8');
|
|
207
|
+
for await (const chunk of process.stdin) {
|
|
208
|
+
const text = String(chunk);
|
|
209
|
+
bytes += Buffer.byteLength(text, 'utf8');
|
|
210
|
+
if (bytes > MAX_PARENT_SUMMARY_STDIN_BYTES) {
|
|
211
|
+
return { ok: false, blocker: `naruto_parent_summary_stdin_too_large:${MAX_PARENT_SUMMARY_STDIN_BYTES}` };
|
|
212
|
+
}
|
|
213
|
+
value += text;
|
|
214
|
+
}
|
|
215
|
+
if (!value.trim())
|
|
216
|
+
return { ok: false, blocker: 'naruto_parent_summary_stdin_empty' };
|
|
217
|
+
return { ok: true, value };
|
|
218
|
+
}
|
|
219
|
+
function sameParentAuthoredSummary(left, right) {
|
|
220
|
+
if (!left || typeof left !== 'object' || Array.isArray(left))
|
|
221
|
+
return false;
|
|
222
|
+
if (!right || typeof right !== 'object' || Array.isArray(right))
|
|
223
|
+
return false;
|
|
224
|
+
const omitHostReceipts = (value) => {
|
|
225
|
+
const { artifacts: _artifacts, capabilities_used: _capabilitiesUsed, ...authored } = value;
|
|
226
|
+
return authored;
|
|
227
|
+
};
|
|
228
|
+
return isDeepStrictEqual(omitHostReceipts(left), omitHostReceipts(right));
|
|
229
|
+
}
|
|
230
|
+
function blockedParentSummary(parsed, blockers) {
|
|
231
|
+
const result = {
|
|
232
|
+
schema: NARUTO_RESULT_SCHEMA,
|
|
233
|
+
action: 'parent-summary',
|
|
234
|
+
ok: false,
|
|
235
|
+
accepted: false,
|
|
236
|
+
status: 'blocked',
|
|
237
|
+
mission_id: parsed.missionId || null,
|
|
238
|
+
blockers: uniqueStrings(blockers)
|
|
239
|
+
};
|
|
240
|
+
return emit(parsed, result, () => {
|
|
241
|
+
for (const line of renderNarutoBlockedLines(result.blockers))
|
|
242
|
+
console.error(line);
|
|
243
|
+
}, true);
|
|
244
|
+
}
|
|
46
245
|
async function narutoRun(parsed) {
|
|
47
246
|
const root = await sksRoot();
|
|
48
247
|
const appSession = detectCodexAppSession();
|
|
@@ -152,6 +351,7 @@ async function narutoRunTransaction(parsed, root, appSession, sessionKey, resolv
|
|
|
152
351
|
missionId: id,
|
|
153
352
|
workflowRunId,
|
|
154
353
|
sessionKey,
|
|
354
|
+
credentialPolicy: parsed.credentialPolicy,
|
|
155
355
|
...(missionLease ? { onChildSpawn: missionLease.protectChildPid } : {})
|
|
156
356
|
});
|
|
157
357
|
const result = await withOfficialSubagentLifecycleLock(dir, async () => {
|
|
@@ -482,6 +682,7 @@ function narutoHelp(parsed) {
|
|
|
482
682
|
console.log(`Nesting: max_depth=${result.max_depth}; Naruto children must not spawn children`);
|
|
483
683
|
console.log('Context: bounded TriWiki attention.use_first anchors with on-demand source hydration');
|
|
484
684
|
console.log('Evidence: SubagentStop is lifecycle-only; completion requires subagent-parent-summary.json with one structured outcome per Naruto child thread.');
|
|
685
|
+
console.log('Codex App finalization: submit the structured object with parent-summary --mission <id> --stdin, then return localized Markdown without exposing JSON.');
|
|
485
686
|
});
|
|
486
687
|
}
|
|
487
688
|
async function resolveRunMission(root, parsed, sessionKey = null) {
|
|
@@ -541,7 +742,7 @@ export function parseNarutoArgs(args) {
|
|
|
541
742
|
: args;
|
|
542
743
|
const first = normalized[0] && !normalized[0].startsWith('-') ? normalized[0] : '';
|
|
543
744
|
const actionName = first;
|
|
544
|
-
const actions = new Set(['run', 'status', 'subagents', 'proof', 'help']);
|
|
745
|
+
const actions = new Set(['run', 'status', 'subagents', 'proof', 'parent-summary', 'help']);
|
|
545
746
|
const action = (actions.has(actionName) ? actionName : 'run');
|
|
546
747
|
const explicitAction = actions.has(actionName);
|
|
547
748
|
const rest = explicitAction ? normalized.slice(1) : normalized;
|
|
@@ -571,7 +772,7 @@ export function parseNarutoArgs(args) {
|
|
|
571
772
|
? positional.join(' ').trim()
|
|
572
773
|
: '';
|
|
573
774
|
const positionalHead = String(positional[0] || '').toLowerCase();
|
|
574
|
-
const subcommandNames = new Set(['run', 'status', 'subagents', 'proof', 'help']);
|
|
775
|
+
const subcommandNames = new Set(['run', 'status', 'subagents', 'proof', 'parent-summary', 'help']);
|
|
575
776
|
if (!first && !explicitAction && positionalHead && !subcommandNames.has(positionalHead)) {
|
|
576
777
|
argumentErrors.push(`unknown_subcommand:${positionalHead}`);
|
|
577
778
|
}
|
|
@@ -599,6 +800,22 @@ export function parseNarutoArgs(args) {
|
|
|
599
800
|
}
|
|
600
801
|
if (action === 'run' && !prompt)
|
|
601
802
|
argumentErrors.push('empty_task');
|
|
803
|
+
if (action === 'parent-summary') {
|
|
804
|
+
if (!missionOption.present || !missionOption.value || missionOption.value === 'latest') {
|
|
805
|
+
argumentErrors.push('parent_summary_requires_explicit_mission');
|
|
806
|
+
}
|
|
807
|
+
if (missionIdOption.present)
|
|
808
|
+
argumentErrors.push('parent_summary_mission_id_alias_not_supported');
|
|
809
|
+
if (!normalized.includes('--stdin'))
|
|
810
|
+
argumentErrors.push('parent_summary_requires_stdin');
|
|
811
|
+
if (agentsOption.present || maxThreadsOption.present || normalized.includes('--readonly')
|
|
812
|
+
|| normalized.includes('--read-only') || normalized.includes('--trusted-project')) {
|
|
813
|
+
argumentErrors.push('parent_summary_unsupported_run_option');
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
else if (normalized.includes('--stdin')) {
|
|
817
|
+
argumentErrors.push('stdin_only_supported_for_parent_summary');
|
|
818
|
+
}
|
|
602
819
|
return {
|
|
603
820
|
action,
|
|
604
821
|
prompt,
|
|
@@ -606,17 +823,28 @@ export function parseNarutoArgs(args) {
|
|
|
606
823
|
maxThreads,
|
|
607
824
|
missionId: String(missionFlag || positionalMission || 'latest'),
|
|
608
825
|
json: normalized.includes('--json'),
|
|
826
|
+
stdin: normalized.includes('--stdin'),
|
|
609
827
|
readOnly: normalized.includes('--readonly') || normalized.includes('--read-only'),
|
|
610
828
|
trustedProject: optionArgs.includes('--trusted-project'),
|
|
611
|
-
argumentErrors: uniqueStrings(argumentErrors)
|
|
829
|
+
argumentErrors: uniqueStrings(argumentErrors),
|
|
830
|
+
credentialPolicy: resolveNarutoCredentialPolicy({
|
|
831
|
+
args: optionArgs,
|
|
832
|
+
env: process.env,
|
|
833
|
+
defaultParentModel: NARUTO_PARENT_MODEL,
|
|
834
|
+
defaultParentEffort: NARUTO_PARENT_EFFORT,
|
|
835
|
+
defaultSubagentModel: DEFAULT_SUBAGENT_MODEL,
|
|
836
|
+
defaultSubagentEffort: DEFAULT_SUBAGENT_EFFORT
|
|
837
|
+
})
|
|
612
838
|
};
|
|
613
839
|
}
|
|
614
840
|
function positionalValues(args) {
|
|
615
841
|
const valueFlags = new Set([
|
|
616
|
-
'--agents', '--max-threads', '--mission', '--mission-id'
|
|
842
|
+
'--agents', '--max-threads', '--mission', '--mission-id',
|
|
843
|
+
'--auth-mode', '--model-provider', '--provider-env-key',
|
|
844
|
+
'--parent-model', '--parent-effort', '--subagent-model', '--subagent-effort'
|
|
617
845
|
]);
|
|
618
846
|
const booleanFlags = new Set([
|
|
619
|
-
'--json', '--readonly', '--read-only', '--trusted-project'
|
|
847
|
+
'--json', '--stdin', '--readonly', '--read-only', '--trusted-project', '--no-forced-login-method'
|
|
620
848
|
]);
|
|
621
849
|
const result = [];
|
|
622
850
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -679,7 +907,7 @@ function optionErrors(name, option, numeric) {
|
|
|
679
907
|
function unknownOptionErrors(args) {
|
|
680
908
|
const canonical = new Set([
|
|
681
909
|
'--agents', '--max-threads', '--mission', '--mission-id',
|
|
682
|
-
'--json', '--readonly', '--read-only', '--trusted-project', '--help', '-h', '--'
|
|
910
|
+
'--json', '--stdin', '--readonly', '--read-only', '--trusted-project', '--help', '-h', '--'
|
|
683
911
|
]);
|
|
684
912
|
const errors = [];
|
|
685
913
|
const optionArgs = args.includes('--') ? args.slice(0, args.indexOf('--')) : args;
|
|
@@ -693,7 +921,7 @@ function unknownOptionErrors(args) {
|
|
|
693
921
|
return errors;
|
|
694
922
|
}
|
|
695
923
|
function booleanOptionErrors(args) {
|
|
696
|
-
const booleanNames = new Set(['--json', '--readonly', '--read-only', '--trusted-project', '--help', '-h']);
|
|
924
|
+
const booleanNames = new Set(['--json', '--stdin', '--readonly', '--read-only', '--trusted-project', '--no-forced-login-method', '--help', '-h']);
|
|
697
925
|
const optionArgs = args.includes('--') ? args.slice(0, args.indexOf('--')) : args;
|
|
698
926
|
const errors = [];
|
|
699
927
|
for (const arg of optionArgs) {
|
|
@@ -785,6 +1013,15 @@ function renderRunResult(result) {
|
|
|
785
1013
|
if (result.status === 'delegation_context_ready')
|
|
786
1014
|
console.log('Continue in the current Naruto parent and wait for every requested child before summarizing.');
|
|
787
1015
|
}
|
|
1016
|
+
function renderParentSummaryResult(result) {
|
|
1017
|
+
if (result.ok !== true) {
|
|
1018
|
+
for (const line of renderNarutoBlockedLines(result.blockers || []))
|
|
1019
|
+
console.log(line);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
console.log(`$sks-naruto parent summary committed: ${result.mission_id}`);
|
|
1023
|
+
console.log(`Workflow run: ${result.workflow_run_id}`);
|
|
1024
|
+
}
|
|
788
1025
|
function renderStatusResult(result) {
|
|
789
1026
|
if (['blocked', 'incomplete'].includes(result.status) && Array.isArray(result.blockers) && result.blockers.length) {
|
|
790
1027
|
for (const line of renderNarutoBlockedLines(result.blockers))
|
|
@@ -47,7 +47,7 @@ export async function pptCommand(command, args = []) {
|
|
|
47
47
|
process.exitCode = 1;
|
|
48
48
|
if (flag(args, '--json'))
|
|
49
49
|
return printJson(result);
|
|
50
|
-
console.error('PPT build blocked: Codex
|
|
50
|
+
console.error('PPT build blocked: no selected Codex imagegen/gpt-image-2 provider is ready.');
|
|
51
51
|
for (const action of imagegenRequired.blocker?.next_actions || [])
|
|
52
52
|
console.error(`- ${action}`);
|
|
53
53
|
return result;
|
|
@@ -159,7 +159,7 @@ async function pptImagegenReview(root, command, action, args = []) {
|
|
|
159
159
|
process.exitCode = 1;
|
|
160
160
|
if (flag(args, '--json'))
|
|
161
161
|
return printJson(result);
|
|
162
|
-
console.error('PPT imagegen review blocked: Codex
|
|
162
|
+
console.error('PPT imagegen review blocked: no selected Codex imagegen/gpt-image-2 provider is ready.');
|
|
163
163
|
for (const action of imagegenRequired.blocker?.next_actions || [])
|
|
164
164
|
console.error(`- ${action}`);
|
|
165
165
|
return result;
|
|
@@ -2,7 +2,13 @@ import path from 'node:path';
|
|
|
2
2
|
import { isHelpRequest } from '../../cli/help.js';
|
|
3
3
|
import { globalSksRoot, projectRoot, readJson, readStdin } from '../fsx.js';
|
|
4
4
|
import { RemoteCodexSessionBindingStore, loadRemoteMachineRegistry, loadRemoteSessionIndex, remoteCodexSessionBindingsPath, remoteMachineRegistryPath, remoteSessionIndexPath, validateRemoteMachineRegistry, validateRemoteSessionIndex } from '../remote/index.js';
|
|
5
|
-
import { loadTelegramConfig, resolveTelegramBotToken, TelegramActionBroker, TelegramAuditLedger, TelegramBotApiClient, TelegramHubRouter, TelegramHubRuntime, TelegramIdempotencyLedger, TelegramOwnerLock, TelegramMessageProjector, TelegramPollingHub, TelegramTopicRegistry, installAndStartTelegramHubService, restartTelegramHubService, setupTelegramLocalCoding, stopTelegramHubService, telegramHubPaths, telegramHubServiceStatus, telegramTokenFingerprint, validateTelegramConfig, validateTelegramPrivatePairing } from '../telegram/index.js';
|
|
5
|
+
import { loadTelegramConfig, probeTelegramBotReadiness, resolveTelegramBotToken, TelegramActionBroker, TelegramAuditLedger, TelegramBotApiClient, TelegramBotApiError, TelegramHubRouter, TelegramHubRuntime, TelegramIdempotencyLedger, TelegramOwnerLock, TelegramMessageProjector, TelegramPollingHub, TelegramTopicRegistry, installAndStartTelegramHubService, restartTelegramHubService, setupTelegramLocalCoding, stopTelegramHubService, telegramHubPaths, telegramHubServiceStatus, telegramTokenFingerprint, validateTelegramConfig, validateTelegramPrivatePairing } from '../telegram/index.js';
|
|
6
|
+
export const TELEGRAM_STATUS_NATIVE_DEADLINE_MS = 8_000;
|
|
7
|
+
export const TELEGRAM_STATUS_TOKEN_LOOKUP_TIMEOUT_MS = 1_000;
|
|
8
|
+
export const TELEGRAM_STATUS_BOT_REQUEST_TIMEOUT_MS = 1_500;
|
|
9
|
+
export const TELEGRAM_STATUS_BOT_MAX_RETRIES = 0;
|
|
10
|
+
export const TELEGRAM_STATUS_LIVE_PROBE_BUDGET_MS = TELEGRAM_STATUS_TOKEN_LOOKUP_TIMEOUT_MS
|
|
11
|
+
+ TELEGRAM_STATUS_BOT_REQUEST_TIMEOUT_MS * 2;
|
|
6
12
|
export async function telegramCommand(args = []) {
|
|
7
13
|
if (isHelpRequest(args)) {
|
|
8
14
|
printTelegramUsage();
|
|
@@ -79,6 +85,7 @@ async function telegramStatus(args, root, paths, configPath) {
|
|
|
79
85
|
const owner = await readJson(paths.owner, null);
|
|
80
86
|
const topics = await new TelegramTopicRegistry(paths.topics).list();
|
|
81
87
|
const controllingRoot = path.resolve(readOption(args, '--project-root') ?? await projectRoot());
|
|
88
|
+
const servicePromise = telegramHubServiceStatus({ projectRoot: controllingRoot, globalRoot: root });
|
|
82
89
|
const machineRegistryRaw = await readJson(readOption(args, '--machines') ?? remoteMachineRegistryPath(root), null);
|
|
83
90
|
const machineValidation = validateRemoteMachineRegistry(machineRegistryRaw);
|
|
84
91
|
const sessionIndexRaw = await readJson(readOption(args, '--session-index') ?? remoteSessionIndexPath(controllingRoot), null);
|
|
@@ -90,17 +97,34 @@ async function telegramStatus(args, root, paths, configPath) {
|
|
|
90
97
|
const registered = bindings.filter((binding) => targets.some((target) => (binding.machine_id === target.machine_id
|
|
91
98
|
&& binding.project_id === target.project_id
|
|
92
99
|
&& path.resolve(binding.project_root) === path.resolve(target.project_root))));
|
|
93
|
-
const service = await telegramHubServiceStatus({ projectRoot: controllingRoot, globalRoot: root });
|
|
94
100
|
let tokenConfigured = false;
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
let liveProbe = telegramLiveProbeStatus(null);
|
|
102
|
+
const probeConfig = validation.config ?? telegramConfigForStatusProbe(rawConfig);
|
|
103
|
+
if (probeConfig) {
|
|
104
|
+
const token = await resolveTelegramBotToken(probeConfig.bot_token_ref, {
|
|
105
|
+
timeoutMs: TELEGRAM_STATUS_TOKEN_LOOKUP_TIMEOUT_MS
|
|
106
|
+
}).catch(() => null);
|
|
107
|
+
tokenConfigured = token !== null;
|
|
108
|
+
if (token) {
|
|
109
|
+
try {
|
|
110
|
+
liveProbe = telegramLiveProbeStatus(await probeTelegramBotReadiness(new TelegramBotApiClient(token, {
|
|
111
|
+
timeoutMs: TELEGRAM_STATUS_BOT_REQUEST_TIMEOUT_MS,
|
|
112
|
+
maxRetries: TELEGRAM_STATUS_BOT_MAX_RETRIES
|
|
113
|
+
})));
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
liveProbe = telegramLiveProbeStatus(null, error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
97
119
|
}
|
|
120
|
+
const service = await servicePromise;
|
|
98
121
|
const pairing = telegramPairingReadiness(rawConfig);
|
|
99
122
|
const blockers = [
|
|
100
123
|
...validation.issues.map((issue) => `config:${issue}`),
|
|
101
124
|
...machineValidation.issues.map((issue) => `machine:${issue}`),
|
|
102
125
|
...sessionValidation.issues.map((issue) => `target:${issue}`),
|
|
103
126
|
...(tokenConfigured ? [] : ['telegram_token_not_available']),
|
|
127
|
+
...(liveProbe.blocker ? [liveProbe.blocker] : []),
|
|
104
128
|
...(pairing.blocker ? [pairing.blocker] : []),
|
|
105
129
|
...(registered.length ? [] : ['no_registered_codex_session']),
|
|
106
130
|
...(service.running ? [] : ['telegram_hub_not_running'])
|
|
@@ -110,6 +134,12 @@ async function telegramStatus(args, root, paths, configPath) {
|
|
|
110
134
|
ok: blockers.length === 0,
|
|
111
135
|
configured: rawConfig !== null,
|
|
112
136
|
token_configured: tokenConfigured,
|
|
137
|
+
bot_verified: liveProbe.bot_verified,
|
|
138
|
+
bot: liveProbe.bot,
|
|
139
|
+
webhook_configured: liveProbe.webhook_configured,
|
|
140
|
+
pending_update_count: liveProbe.pending_update_count,
|
|
141
|
+
long_poll_ready: liveProbe.long_poll_ready,
|
|
142
|
+
telegram_probe_error: liveProbe.telegram_probe_error,
|
|
113
143
|
pairing_valid: pairing.pairing_valid,
|
|
114
144
|
pairing_issues: pairing.pairing_issues,
|
|
115
145
|
hub_running: service.running,
|
|
@@ -135,7 +165,8 @@ async function telegramStatus(args, root, paths, configPath) {
|
|
|
135
165
|
updated_at: binding.updated_at
|
|
136
166
|
})),
|
|
137
167
|
remote_config_issues: [...machineValidation.issues, ...sessionValidation.issues],
|
|
138
|
-
blockers
|
|
168
|
+
blockers,
|
|
169
|
+
next_actions: telegramNextActions(blockers)
|
|
139
170
|
};
|
|
140
171
|
}
|
|
141
172
|
function printTelegramUsage() {
|
|
@@ -150,26 +181,49 @@ Usage:
|
|
|
150
181
|
sks telegram hub run [--project-root <path>] [--json]
|
|
151
182
|
|
|
152
183
|
Pairing (do these first):
|
|
153
|
-
1.
|
|
184
|
+
1. Open @BotFather, send /newbot, choose a name and a unique username ending in "bot",
|
|
185
|
+
then copy the HTTP API token BotFather returns.
|
|
154
186
|
2. Send /start to that bot from the Telegram account you want to pair.
|
|
155
187
|
3. printf '%s' "<token>" | sks telegram setup --bot-token-stdin --project-root "$PWD" --json
|
|
156
188
|
The token is read from stdin only and stored in the macOS Keychain.
|
|
157
189
|
4. sks telegram hub start --project-root "$PWD" --json
|
|
158
190
|
|
|
191
|
+
If status reports telegram_webhook_conflict, remove the webhook in the service
|
|
192
|
+
that configured it, then retry. SKS does not delete external webhook state implicitly.
|
|
193
|
+
If setup or the hub reports telegram_409_conflict, stop the other poller using
|
|
194
|
+
this bot token before retrying.
|
|
195
|
+
Stop the SKS Telegram hub before rerunning setup or rotating its BotFather token.
|
|
196
|
+
|
|
159
197
|
The same flow is available in SKS Center → Remote & Telegram.
|
|
160
198
|
See docs/telegram-and-center.md for the full guide and troubleshooting.
|
|
161
199
|
`);
|
|
162
200
|
}
|
|
163
201
|
export function telegramPairingReadiness(value) {
|
|
164
202
|
const pairing = validateTelegramPrivatePairing(value);
|
|
203
|
+
const multipleIds = hasMultipleTelegramPairingIds(value);
|
|
165
204
|
return {
|
|
166
205
|
pairing_valid: pairing.ok,
|
|
167
206
|
pairing_issues: pairing.issues,
|
|
168
207
|
blocker: pairing.ok
|
|
169
208
|
? null
|
|
170
|
-
:
|
|
171
|
-
? '
|
|
172
|
-
:
|
|
209
|
+
: multipleIds
|
|
210
|
+
? 'telegram_pairing_multiple_ids_requires_setup'
|
|
211
|
+
: pairing.missing
|
|
212
|
+
? 'telegram_pairing_missing'
|
|
213
|
+
: `telegram_pairing_invalid:${pairing.issues.join(',')}`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export function telegramLiveProbeStatus(readiness, error = null) {
|
|
217
|
+
const probeError = error === null ? null : telegramProbeFailure(error);
|
|
218
|
+
const webhookConfigured = readiness?.webhook_configured === true;
|
|
219
|
+
return {
|
|
220
|
+
bot_verified: readiness !== null,
|
|
221
|
+
bot: readiness?.bot ?? null,
|
|
222
|
+
webhook_configured: webhookConfigured,
|
|
223
|
+
pending_update_count: readiness?.pending_update_count ?? 0,
|
|
224
|
+
long_poll_ready: readiness !== null && !webhookConfigured,
|
|
225
|
+
telegram_probe_error: probeError,
|
|
226
|
+
blocker: probeError ?? (webhookConfigured ? 'telegram_webhook_conflict' : null)
|
|
173
227
|
};
|
|
174
228
|
}
|
|
175
229
|
async function runHub(args, root, paths, configPath, json) {
|
|
@@ -243,7 +297,19 @@ async function runHub(args, root, paths, configPath, json) {
|
|
|
243
297
|
}
|
|
244
298
|
async function assertHubSetupReady(controllingRoot, globalRoot, configPath) {
|
|
245
299
|
const config = await loadTelegramConfig(configPath);
|
|
246
|
-
await resolveTelegramBotToken(config.bot_token_ref);
|
|
300
|
+
const token = await resolveTelegramBotToken(config.bot_token_ref);
|
|
301
|
+
let readiness;
|
|
302
|
+
try {
|
|
303
|
+
readiness = await probeTelegramBotReadiness(new TelegramBotApiClient(token, {
|
|
304
|
+
timeoutMs: 8_000,
|
|
305
|
+
maxRetries: 1
|
|
306
|
+
}));
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
throw new Error(telegramProbeFailure(error));
|
|
310
|
+
}
|
|
311
|
+
if (readiness.webhook_configured)
|
|
312
|
+
throw new Error('telegram_webhook_conflict');
|
|
247
313
|
const registry = await loadRemoteMachineRegistry(remoteMachineRegistryPath(globalRoot));
|
|
248
314
|
const index = await loadRemoteSessionIndex(remoteSessionIndexPath(controllingRoot), registry);
|
|
249
315
|
const bindings = await new RemoteCodexSessionBindingStore(remoteCodexSessionBindingsPath(controllingRoot)).list();
|
|
@@ -284,3 +350,50 @@ function publicError(err) {
|
|
|
284
350
|
.replace(/(?:\/Users|\/home)\/[^\s]+/g, '[path-redacted]')
|
|
285
351
|
.slice(0, 500);
|
|
286
352
|
}
|
|
353
|
+
function telegramProbeFailure(error) {
|
|
354
|
+
return error instanceof TelegramBotApiError && (error.errorCode === 401 || error.errorCode === 404)
|
|
355
|
+
? 'telegram_bot_auth_failed'
|
|
356
|
+
: 'telegram_bot_probe_failed';
|
|
357
|
+
}
|
|
358
|
+
function telegramNextActions(blockers) {
|
|
359
|
+
const actions = [];
|
|
360
|
+
if (blockers.includes('telegram_token_not_available') || blockers.includes('telegram_bot_auth_failed')) {
|
|
361
|
+
actions.push('Create or regenerate the bot token in @BotFather, then rerun setup with --bot-token-stdin.');
|
|
362
|
+
}
|
|
363
|
+
if (blockers.includes('telegram_webhook_conflict')) {
|
|
364
|
+
actions.push('Remove the existing webhook in the service that configured it, then rerun telegram status.');
|
|
365
|
+
}
|
|
366
|
+
if (blockers.includes('telegram_bot_probe_failed')) {
|
|
367
|
+
actions.push('Check network access to api.telegram.org, then rerun telegram status.');
|
|
368
|
+
}
|
|
369
|
+
if (blockers.includes('telegram_pairing_missing') || blockers.some((blocker) => blocker.startsWith('telegram_pairing_invalid:'))) {
|
|
370
|
+
actions.push('Send /start to the bot in a private chat, then rerun telegram setup.');
|
|
371
|
+
}
|
|
372
|
+
if (blockers.includes('telegram_pairing_multiple_ids_requires_setup')) {
|
|
373
|
+
actions.push('Stop the hub, then rerun telegram setup to replace the ambiguous multi-ID config with one verified private chat and user.');
|
|
374
|
+
}
|
|
375
|
+
if (blockers.includes('no_registered_codex_session')) {
|
|
376
|
+
actions.push('Rerun telegram setup for this project root.');
|
|
377
|
+
}
|
|
378
|
+
if (blockers.includes('telegram_hub_not_running')) {
|
|
379
|
+
actions.push('Run sks telegram hub start --project-root \"$PWD\" --json after other blockers are cleared.');
|
|
380
|
+
}
|
|
381
|
+
return actions;
|
|
382
|
+
}
|
|
383
|
+
function hasMultipleTelegramPairingIds(value) {
|
|
384
|
+
if (!value || typeof value !== 'object')
|
|
385
|
+
return false;
|
|
386
|
+
const record = value;
|
|
387
|
+
return (Array.isArray(record.paired_chat_ids) && record.paired_chat_ids.length > 1)
|
|
388
|
+
|| (Array.isArray(record.paired_user_ids) && record.paired_user_ids.length > 1);
|
|
389
|
+
}
|
|
390
|
+
function telegramConfigForStatusProbe(value) {
|
|
391
|
+
if (!value || typeof value !== 'object')
|
|
392
|
+
return null;
|
|
393
|
+
const validation = validateTelegramConfig({
|
|
394
|
+
...value,
|
|
395
|
+
paired_chat_ids: ['1'],
|
|
396
|
+
paired_user_ids: ['1']
|
|
397
|
+
});
|
|
398
|
+
return validation.config;
|
|
399
|
+
}
|
|
@@ -5,11 +5,21 @@ import { computeTriWikiAffectedGraph } from '../triwiki/triwiki-affected-graph.j
|
|
|
5
5
|
import { buildTriWikiGateImpactMap } from '../triwiki/triwiki-gate-impact-map.js';
|
|
6
6
|
import { DEFAULT_TRIWIKI_MODULE_CARDS } from '../triwiki/triwiki-module-card.js';
|
|
7
7
|
import { summarizeTriWikiProofBank } from '../triwiki/triwiki-proof-bank.js';
|
|
8
|
+
import { isTriWikiGraphSubcommand, triwikiGraphCommand } from './triwiki-graph-command.js';
|
|
8
9
|
export async function triwikiCommand(args = []) {
|
|
9
10
|
const root = await projectRoot();
|
|
10
11
|
const sub = args[0] && !args[0].startsWith('-') ? args[0] : 'index';
|
|
11
12
|
const json = flag(args, '--json');
|
|
12
13
|
let result;
|
|
14
|
+
if (isTriWikiGraphSubcommand(sub)) {
|
|
15
|
+
const answer = await triwikiGraphCommand(root, sub, args.slice(1));
|
|
16
|
+
process.exitCode = answer.ok ? 0 : 2;
|
|
17
|
+
if (json)
|
|
18
|
+
return printJson(answer.result);
|
|
19
|
+
for (const line of answer.lines)
|
|
20
|
+
console.log(line);
|
|
21
|
+
return answer.result;
|
|
22
|
+
}
|
|
13
23
|
if (sub === 'index') {
|
|
14
24
|
result = {
|
|
15
25
|
schema: 'sks.triwiki-index.v1',
|
|
@@ -26,7 +36,7 @@ export async function triwikiCommand(args = []) {
|
|
|
26
36
|
result = summarizeTriWikiProofBank(root);
|
|
27
37
|
}
|
|
28
38
|
else {
|
|
29
|
-
console.error('Usage: sks triwiki index|affected|proof-bank [--json]');
|
|
39
|
+
console.error('Usage: sks triwiki index|affected|proof-bank|graph-status|graph-lint|graph-query [--json]');
|
|
30
40
|
process.exitCode = 1;
|
|
31
41
|
return null;
|
|
32
42
|
}
|