@gmickel/gno 2.8.4 → 2.9.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 +1 -1
- package/assets/spa-production.json.gz +0 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.8.4.zip → gno-browser-clipper-v2.9.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.9.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/bunfig.toml +1 -1
- package/package.json +1 -1
- package/spec/cli.md +13 -5
- package/spec/output-schemas/doctor.schema.json +22 -2
- package/spec/output-schemas/status.schema.json +22 -2
- package/src/cli/commands/doctor-activation.ts +24 -15
- package/src/cli/commands/shared.ts +6 -0
- package/src/cli/commands/status.ts +7 -2
- package/src/converters/adapters/markitdownTs/adapter.ts +3 -14
- package/src/converters/adapters/markitdownTs/match.ts +20 -0
- package/src/converters/adapters/officeparser/adapter.ts +3 -8
- package/src/converters/adapters/officeparser/match.ts +13 -0
- package/src/converters/registry.ts +72 -48
- package/src/core/activation-connector-health.ts +99 -7
- package/src/core/activation-status.ts +33 -7
- package/src/core/phase-trace.ts +34 -0
- package/src/core/write-lease.ts +5 -0
- package/src/index.ts +7 -1
- package/src/ingestion/file-processor.ts +3 -0
- package/src/ingestion/sync.ts +10 -0
- package/src/serve/activation-health.ts +27 -17
- package/src/serve/public/components/BootstrapStatus.tsx +8 -0
- package/src/store/sqlite/adapter.ts +6 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.8.4.zip.sha256 +0 -1
package/README.md
CHANGED
|
@@ -139,7 +139,7 @@ See the [guide](docs/COMPILED-CONTEXT.md).
|
|
|
139
139
|
|
|
140
140
|
<!-- public-truth:current-version -->
|
|
141
141
|
|
|
142
|
-
> Current source version: **v2.
|
|
142
|
+
> Current source version: **v2.9.0**. See [CHANGELOG.md](./CHANGELOG.md).
|
|
143
143
|
|
|
144
144
|
<!-- /public-truth -->
|
|
145
145
|
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2d8fa6721fe305e2fd70450405e255900d028b187c884bbf74f29a3a33d27d3c gno-browser-clipper-v2.9.0.zip
|
package/bunfig.toml
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
plugins = ["bun-plugin-tailwind"]
|
|
3
3
|
|
|
4
4
|
[test]
|
|
5
|
-
preload = ["./test/preload/happy-dom.ts"]
|
|
5
|
+
preload = ["./test/preload/happy-dom.ts", "./test/preload/env-guard.ts"]
|
|
6
6
|
# Frozen evidence and local experiments are not the executable regression suite.
|
|
7
7
|
pathIgnorePatterns = [".flow/artifacts/**", "notes/**"]
|
package/package.json
CHANGED
package/spec/cli.md
CHANGED
|
@@ -406,11 +406,19 @@ Passive callers report `semantic_not_checked` when vector runtime availability
|
|
|
406
406
|
is unknown. `vector_unavailable` is reserved for a resident runtime that has
|
|
407
407
|
positively reported vector search unavailable.
|
|
408
408
|
|
|
409
|
-
`connectorProjection.total` counts every
|
|
410
|
-
target pair
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
409
|
+
`connectorProjection.total` counts every non-empty collection and connector
|
|
410
|
+
target pair. Every pair is evaluated from cached receipts; the 64-pair cap
|
|
411
|
+
bounds only the listed `connectors`. `projected` equals `connectors.length`,
|
|
412
|
+
and `truncated` is true exactly when `total > projected`. When truncated,
|
|
413
|
+
`omitted` counts the unlisted pairs by outcome (`passed`, `failed`,
|
|
414
|
+
`incomplete`, `notApplicable`; the counts sum to `total - projected`). Connector
|
|
415
|
+
completeness, and so overall health, is decided from the listed pairs plus
|
|
416
|
+
these counts: a truncated projection whose pairs all passed or are not
|
|
417
|
+
applicable is healthy, and a failed or pending pair beyond the list keeps it
|
|
418
|
+
degraded. Human-readable output keeps the `N/M ... shown; K omitted` line and
|
|
419
|
+
summarizes the omitted outcomes, for example `(108 passed, 1 failed)`. A
|
|
420
|
+
truncated projection without `omitted` claims no result for the unlisted pairs
|
|
421
|
+
and is never reported healthy.
|
|
414
422
|
|
|
415
423
|
**Exit Codes:**
|
|
416
424
|
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
"total": {
|
|
165
165
|
"type": "integer",
|
|
166
166
|
"minimum": 0,
|
|
167
|
-
"description": "All
|
|
167
|
+
"description": "All non-empty collection and connector target pairs; every pair is evaluated from cached receipts."
|
|
168
168
|
},
|
|
169
169
|
"projected": {
|
|
170
170
|
"type": "integer",
|
|
@@ -174,7 +174,27 @@
|
|
|
174
174
|
},
|
|
175
175
|
"truncated": {
|
|
176
176
|
"type": "boolean",
|
|
177
|
-
"description": "True exactly when total is greater than projected
|
|
177
|
+
"description": "True exactly when total is greater than projected. Omitted pairs are still evaluated; their outcomes are counted in omitted."
|
|
178
|
+
},
|
|
179
|
+
"omitted": {
|
|
180
|
+
"type": "object",
|
|
181
|
+
"additionalProperties": false,
|
|
182
|
+
"required": ["passed", "failed", "incomplete", "notApplicable"],
|
|
183
|
+
"description": "Present when truncated: outcome counts for the evaluated pairs not listed in connectors. The counts sum to total - projected. Connector completeness is decided from connectors plus these counts.",
|
|
184
|
+
"properties": {
|
|
185
|
+
"passed": { "type": "integer", "minimum": 0 },
|
|
186
|
+
"failed": { "type": "integer", "minimum": 0 },
|
|
187
|
+
"incomplete": {
|
|
188
|
+
"type": "integer",
|
|
189
|
+
"minimum": 0,
|
|
190
|
+
"description": "Pending, or skipped for a runtime reason such as lexical retrieval not ready."
|
|
191
|
+
},
|
|
192
|
+
"notApplicable": {
|
|
193
|
+
"type": "integer",
|
|
194
|
+
"minimum": 0,
|
|
195
|
+
"description": "Connector not configured, or a skill runtime that cannot be verified."
|
|
196
|
+
}
|
|
197
|
+
}
|
|
178
198
|
}
|
|
179
199
|
}
|
|
180
200
|
}
|
|
@@ -514,7 +514,7 @@
|
|
|
514
514
|
"total": {
|
|
515
515
|
"type": "integer",
|
|
516
516
|
"minimum": 0,
|
|
517
|
-
"description": "All
|
|
517
|
+
"description": "All non-empty collection and connector target pairs; every pair is evaluated from cached receipts."
|
|
518
518
|
},
|
|
519
519
|
"projected": {
|
|
520
520
|
"type": "integer",
|
|
@@ -524,7 +524,27 @@
|
|
|
524
524
|
},
|
|
525
525
|
"truncated": {
|
|
526
526
|
"type": "boolean",
|
|
527
|
-
"description": "True exactly when total is greater than projected
|
|
527
|
+
"description": "True exactly when total is greater than projected. Omitted pairs are still evaluated; their outcomes are counted in omitted."
|
|
528
|
+
},
|
|
529
|
+
"omitted": {
|
|
530
|
+
"type": "object",
|
|
531
|
+
"additionalProperties": false,
|
|
532
|
+
"required": ["passed", "failed", "incomplete", "notApplicable"],
|
|
533
|
+
"description": "Present when truncated: outcome counts for the evaluated pairs not listed in connectors. The counts sum to total - projected. Connector completeness is decided from connectors plus these counts.",
|
|
534
|
+
"properties": {
|
|
535
|
+
"passed": { "type": "integer", "minimum": 0 },
|
|
536
|
+
"failed": { "type": "integer", "minimum": 0 },
|
|
537
|
+
"incomplete": {
|
|
538
|
+
"type": "integer",
|
|
539
|
+
"minimum": 0,
|
|
540
|
+
"description": "Pending, or skipped for a runtime reason such as lexical retrieval not ready."
|
|
541
|
+
},
|
|
542
|
+
"notApplicable": {
|
|
543
|
+
"type": "integer",
|
|
544
|
+
"minimum": 0,
|
|
545
|
+
"description": "Connector not configured, or a skill runtime that cannot be verified."
|
|
546
|
+
}
|
|
547
|
+
}
|
|
528
548
|
}
|
|
529
549
|
}
|
|
530
550
|
}
|
|
@@ -6,6 +6,11 @@ import type { StorePort } from "../../store/types";
|
|
|
6
6
|
import type { DoctorCheck } from "./doctor";
|
|
7
7
|
|
|
8
8
|
import { getIndexDbPath, getModelsCachePath } from "../../app/constants";
|
|
9
|
+
import {
|
|
10
|
+
classifyConnectorProof,
|
|
11
|
+
describeOmittedConnectorPairs,
|
|
12
|
+
omittedConnectorSummary,
|
|
13
|
+
} from "../../core/activation-connector-health";
|
|
9
14
|
import { isEmptyActivationCollection } from "../../core/activation-empty";
|
|
10
15
|
import { buildActivationStatus } from "../../core/activation-status";
|
|
11
16
|
import { ModelCache } from "../../llm/cache";
|
|
@@ -149,13 +154,14 @@ export function checkConnectorActivation(
|
|
|
149
154
|
activation: ActivationStatus
|
|
150
155
|
): DoctorCheck | null {
|
|
151
156
|
const { projected, total, truncated } = activation.connectorProjection;
|
|
152
|
-
const omitted =
|
|
157
|
+
const omitted = omittedConnectorSummary(activation);
|
|
153
158
|
const observed = activation.connectors.filter(
|
|
154
|
-
(
|
|
155
|
-
code !== "connector_not_configured" &&
|
|
156
|
-
code !== "target_runtime_unverifiable"
|
|
159
|
+
(connector) => classifyConnectorProof(connector) !== "notApplicable"
|
|
157
160
|
);
|
|
158
|
-
|
|
161
|
+
const omittedObserved = omitted
|
|
162
|
+
? omitted.passed + omitted.failed + omitted.incomplete
|
|
163
|
+
: 0;
|
|
164
|
+
if (observed.length === 0 && omittedObserved === 0 && omitted) {
|
|
159
165
|
return null;
|
|
160
166
|
}
|
|
161
167
|
const incomplete = observed.filter(({ status }) => status !== "passed");
|
|
@@ -163,20 +169,23 @@ export function checkConnectorActivation(
|
|
|
163
169
|
({ collection, target, status, code, remediation }) =>
|
|
164
170
|
`${target}/${collection}: ${status}${code ? `/${code}` : ""}${remediation ? `. ${remediation}` : ""}`
|
|
165
171
|
);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
);
|
|
172
|
+
const omittedDetail = describeOmittedConnectorPairs(activation);
|
|
173
|
+
if (omittedDetail) {
|
|
174
|
+
details.unshift(omittedDetail);
|
|
170
175
|
}
|
|
176
|
+
const unresolved =
|
|
177
|
+
incomplete.length + (omitted ? omitted.failed + omitted.incomplete : 0);
|
|
178
|
+
const passed = observed.length - incomplete.length + (omitted?.passed ?? 0);
|
|
179
|
+
const listing = truncated ? ` (${projected} of ${total} checks listed)` : "";
|
|
171
180
|
return {
|
|
172
181
|
name: "connector-activation",
|
|
173
|
-
status:
|
|
182
|
+
status: unresolved > 0 || !omitted ? "warn" : "ok",
|
|
174
183
|
message:
|
|
175
|
-
|
|
176
|
-
? `${
|
|
177
|
-
:
|
|
178
|
-
? `${
|
|
179
|
-
: `${
|
|
184
|
+
unresolved > 0
|
|
185
|
+
? `${unresolved} connector proof${unresolved === 1 ? "" : "s"} pending or failed${listing}`
|
|
186
|
+
: omitted
|
|
187
|
+
? `${passed} connector proof${passed === 1 ? "" : "s"} passed${listing}`
|
|
188
|
+
: `${projected} of ${total} connector target/collection checks projected`,
|
|
180
189
|
details,
|
|
181
190
|
};
|
|
182
191
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
loadConfig,
|
|
21
21
|
writeConfigWarningsToStderr,
|
|
22
22
|
} from "../../config";
|
|
23
|
+
import { tracePhase } from "../../core/phase-trace";
|
|
23
24
|
import { BUDGET_ERROR_CODES } from "../../ingestion/file-processor";
|
|
24
25
|
import { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
25
26
|
import { assertCliSessionBinding } from "../session-binding";
|
|
@@ -63,6 +64,7 @@ export interface InitStoreOptions {
|
|
|
63
64
|
export async function initStore(
|
|
64
65
|
options: InitStoreOptions = {}
|
|
65
66
|
): Promise<InitStoreResult> {
|
|
67
|
+
tracePhase("store: init");
|
|
66
68
|
// Check if initialized
|
|
67
69
|
const initialized = await isInitialized(options.configPath);
|
|
68
70
|
if (!initialized) {
|
|
@@ -119,11 +121,13 @@ export async function initStore(
|
|
|
119
121
|
// Set configPath for status output
|
|
120
122
|
store.setConfigPath(actualConfigPath);
|
|
121
123
|
|
|
124
|
+
tracePhase("store: opening index");
|
|
122
125
|
const openResult = await store.open(
|
|
123
126
|
dbPath,
|
|
124
127
|
config.ftsTokenizer,
|
|
125
128
|
config.busyTimeoutMs
|
|
126
129
|
);
|
|
130
|
+
tracePhase("store: index open");
|
|
127
131
|
if (!openResult.ok) {
|
|
128
132
|
return { ok: false, error: openResult.error.message };
|
|
129
133
|
}
|
|
@@ -139,6 +143,7 @@ export async function initStore(
|
|
|
139
143
|
return { ok: false, error: syncCollResult.error.message };
|
|
140
144
|
}
|
|
141
145
|
|
|
146
|
+
tracePhase("store: collections synced");
|
|
142
147
|
// Sync contexts from config to DB
|
|
143
148
|
const syncCtxResult = await store.syncContexts(config.contexts ?? []);
|
|
144
149
|
if (!syncCtxResult.ok) {
|
|
@@ -146,6 +151,7 @@ export async function initStore(
|
|
|
146
151
|
return { ok: false, error: syncCtxResult.error.message };
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
tracePhase("store: contexts synced");
|
|
149
155
|
return { ok: true, store, config, collections, actualConfigPath };
|
|
150
156
|
}
|
|
151
157
|
|
|
@@ -18,7 +18,11 @@ import {
|
|
|
18
18
|
isInitialized,
|
|
19
19
|
loadConfig,
|
|
20
20
|
} from "../../config";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
describeConnectorSummary,
|
|
23
|
+
isConnectorActivationComplete,
|
|
24
|
+
omittedConnectorSummary,
|
|
25
|
+
} from "../../core/activation-connector-health";
|
|
22
26
|
import { isEmptyActivationCollection } from "../../core/activation-empty";
|
|
23
27
|
import { buildActivationStatus } from "../../core/activation-status";
|
|
24
28
|
import { formatChunkingStatus } from "../../core/chunking-status";
|
|
@@ -146,7 +150,8 @@ function connectorProjectionLine(activation: ActivationStatus): string | null {
|
|
|
146
150
|
if (!truncated) {
|
|
147
151
|
return null;
|
|
148
152
|
}
|
|
149
|
-
|
|
153
|
+
const omitted = omittedConnectorSummary(activation);
|
|
154
|
+
return `Connector projection: ${projected}/${total} target/collection checks shown; ${total - projected} omitted${omitted ? ` (${describeConnectorSummary(omitted)})` : ", no result claimed"}`;
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
function isStatusHealthy(
|
|
@@ -20,20 +20,11 @@ import {
|
|
|
20
20
|
} from "../../errors";
|
|
21
21
|
import { ADAPTER_VERSIONS } from "../../versions";
|
|
22
22
|
import { hasPrefix, isPasswordProtectedXlsx } from "../shared/ooxml-protection";
|
|
23
|
+
import { MARKITDOWN_CONVERTER_ID, markitdownCanHandle } from "./match";
|
|
23
24
|
|
|
24
|
-
const CONVERTER_ID =
|
|
25
|
+
const CONVERTER_ID = MARKITDOWN_CONVERTER_ID;
|
|
25
26
|
const CONVERTER_VERSION = ADAPTER_VERSIONS["markitdown-ts"];
|
|
26
27
|
|
|
27
|
-
/** Supported extensions for this adapter */
|
|
28
|
-
const SUPPORTED_EXTENSIONS = [".pdf", ".docx", ".xlsx"];
|
|
29
|
-
|
|
30
|
-
/** Supported MIME types */
|
|
31
|
-
const SUPPORTED_MIMES = [
|
|
32
|
-
"application/pdf",
|
|
33
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
34
|
-
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
35
|
-
];
|
|
36
|
-
|
|
37
28
|
const PDF_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
|
|
38
29
|
const PDF_TRAILER_SCAN_BYTES = 2048;
|
|
39
30
|
const MAX_MESSAGE_LENGTH = 200;
|
|
@@ -114,9 +105,7 @@ export const markitdownAdapter: Converter = {
|
|
|
114
105
|
id: CONVERTER_ID,
|
|
115
106
|
version: CONVERTER_VERSION,
|
|
116
107
|
|
|
117
|
-
canHandle
|
|
118
|
-
return SUPPORTED_EXTENSIONS.includes(ext) || SUPPORTED_MIMES.includes(mime);
|
|
119
|
-
},
|
|
108
|
+
canHandle: markitdownCanHandle,
|
|
120
109
|
|
|
121
110
|
async convert(input: ConvertInput): Promise<ConvertResult> {
|
|
122
111
|
// 1. Check size limit (defense in depth; EPIC 5 does stat-based pre-check)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing for the markitdown-ts adapter, kept apart from the adapter so the
|
|
3
|
+
* registry can route files without loading pdf.js and the Office parsers.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const MARKITDOWN_CONVERTER_ID = "adapter/markitdown-ts" as const;
|
|
7
|
+
|
|
8
|
+
/** Supported extensions for this adapter */
|
|
9
|
+
const SUPPORTED_EXTENSIONS = [".pdf", ".docx", ".xlsx"];
|
|
10
|
+
|
|
11
|
+
/** Supported MIME types */
|
|
12
|
+
const SUPPORTED_MIMES = [
|
|
13
|
+
"application/pdf",
|
|
14
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
15
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function markitdownCanHandle(mime: string, ext: string): boolean {
|
|
19
|
+
return SUPPORTED_EXTENSIONS.includes(ext) || SUPPORTED_MIMES.includes(mime);
|
|
20
|
+
}
|
|
@@ -14,14 +14,11 @@ import { parseOffice } from "../../../../vendor/converters/officeparser";
|
|
|
14
14
|
import { adapterError, corruptError, tooLargeError } from "../../errors";
|
|
15
15
|
import { basenameWithoutExt } from "../../path";
|
|
16
16
|
import { ADAPTER_VERSIONS } from "../../versions";
|
|
17
|
+
import { OFFICEPARSER_CONVERTER_ID, officeparserCanHandle } from "./match";
|
|
17
18
|
|
|
18
|
-
const CONVERTER_ID =
|
|
19
|
+
const CONVERTER_ID = OFFICEPARSER_CONVERTER_ID;
|
|
19
20
|
const CONVERTER_VERSION = ADAPTER_VERSIONS.officeparser;
|
|
20
21
|
|
|
21
|
-
/** Supported MIME type */
|
|
22
|
-
const PPTX_MIME =
|
|
23
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
24
|
-
|
|
25
22
|
/**
|
|
26
23
|
* Control character pattern built dynamically to avoid lint issues.
|
|
27
24
|
* Matches U+0000-U+001F and U+007F (all ASCII control chars).
|
|
@@ -62,9 +59,7 @@ export const officeparserAdapter: Converter = {
|
|
|
62
59
|
id: CONVERTER_ID,
|
|
63
60
|
version: CONVERTER_VERSION,
|
|
64
61
|
|
|
65
|
-
canHandle
|
|
66
|
-
return ext === ".pptx" || mime === PPTX_MIME;
|
|
67
|
-
},
|
|
62
|
+
canHandle: officeparserCanHandle,
|
|
68
63
|
|
|
69
64
|
async convert(input: ConvertInput): Promise<ConvertResult> {
|
|
70
65
|
// Size check (defense in depth; EPIC 5 does stat-based pre-check)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing for the officeparser adapter, kept apart from the adapter so the
|
|
3
|
+
* registry can route files without loading the parser.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const OFFICEPARSER_CONVERTER_ID = "adapter/officeparser" as const;
|
|
7
|
+
|
|
8
|
+
const PPTX_MIME =
|
|
9
|
+
"application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
10
|
+
|
|
11
|
+
export function officeparserCanHandle(mime: string, ext: string): boolean {
|
|
12
|
+
return ext === ".pptx" || mime === PPTX_MIME;
|
|
13
|
+
}
|
|
@@ -5,12 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
import type {
|
|
7
7
|
Converter,
|
|
8
|
+
ConverterId,
|
|
8
9
|
ConvertInput,
|
|
9
10
|
ConvertResult,
|
|
10
11
|
RecordAdapter,
|
|
11
12
|
} from "./types";
|
|
12
13
|
|
|
14
|
+
import {
|
|
15
|
+
MARKITDOWN_CONVERTER_ID,
|
|
16
|
+
markitdownCanHandle,
|
|
17
|
+
} from "./adapters/markitdownTs/match";
|
|
18
|
+
import {
|
|
19
|
+
OFFICEPARSER_CONVERTER_ID,
|
|
20
|
+
officeparserCanHandle,
|
|
21
|
+
} from "./adapters/officeparser/match";
|
|
13
22
|
import { adapterError, unsupportedError } from "./errors";
|
|
23
|
+
import { ADAPTER_VERSIONS } from "./versions";
|
|
14
24
|
|
|
15
25
|
export class ConverterRegistry {
|
|
16
26
|
private readonly converters: Converter[] = [];
|
|
@@ -71,38 +81,56 @@ export class ConverterRegistry {
|
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
/** Identity and routing of an adapter whose module loads on first use. */
|
|
85
|
+
export interface LazyAdapterSpec {
|
|
86
|
+
id: ConverterId;
|
|
87
|
+
version: string;
|
|
88
|
+
canHandle: Converter["canHandle"];
|
|
89
|
+
}
|
|
90
|
+
|
|
74
91
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
92
|
+
* An adapter that loads its module on its first conversion. Routing and
|
|
93
|
+
* identity come from the spec, so selecting a converter or a record adapter
|
|
94
|
+
* never loads the parser: the PDF/Office parsers took 9-11 s to load on a
|
|
95
|
+
* cold Windows runner, inside every `gno update` (fn-201). If the module
|
|
96
|
+
* cannot load in this runtime (pdf.js without its native canvas binding in a
|
|
97
|
+
* standalone executable), its file types fail with ADAPTER_FAILURE and the
|
|
98
|
+
* reason, and other file types are unaffected.
|
|
77
99
|
*/
|
|
78
|
-
export
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
export function lazyAdapter(
|
|
101
|
+
spec: LazyAdapterSpec,
|
|
102
|
+
load: () => Promise<Converter>
|
|
103
|
+
): Converter {
|
|
104
|
+
let loaded: Promise<Converter> | undefined;
|
|
105
|
+
return {
|
|
106
|
+
id: spec.id,
|
|
107
|
+
version: spec.version,
|
|
108
|
+
canHandle: spec.canHandle,
|
|
109
|
+
async convert(input) {
|
|
110
|
+
loaded ??= load().catch((cause: unknown) =>
|
|
111
|
+
unavailableAdapter(
|
|
112
|
+
spec,
|
|
113
|
+
cause instanceof Error ? cause.message : String(cause)
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
return (await loaded).convert(input);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function unavailableAdapter(spec: LazyAdapterSpec, reason: string): Converter {
|
|
122
|
+
return {
|
|
123
|
+
...spec,
|
|
124
|
+
convert: (input) =>
|
|
125
|
+
Promise.resolve({
|
|
126
|
+
ok: false,
|
|
127
|
+
error: adapterError(
|
|
128
|
+
input,
|
|
129
|
+
spec.id,
|
|
130
|
+
`${spec.id} is unavailable in this build: ${reason}`
|
|
131
|
+
),
|
|
132
|
+
}),
|
|
133
|
+
};
|
|
106
134
|
}
|
|
107
135
|
|
|
108
136
|
/**
|
|
@@ -121,27 +149,23 @@ export async function createDefaultRegistry(): Promise<ConverterRegistry> {
|
|
|
121
149
|
const { markdownConverter } = await import("./native/markdown");
|
|
122
150
|
const { plaintextConverter } = await import("./native/plaintext");
|
|
123
151
|
const { xlsxAdapter } = await import("./adapters/xlsx/adapter");
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
152
|
+
const markitdownAdapter = lazyAdapter(
|
|
153
|
+
{
|
|
154
|
+
id: MARKITDOWN_CONVERTER_ID,
|
|
155
|
+
version: ADAPTER_VERSIONS["markitdown-ts"],
|
|
156
|
+
canHandle: markitdownCanHandle,
|
|
157
|
+
},
|
|
128
158
|
async () =>
|
|
129
|
-
(await import("./adapters/markitdownTs/adapter")).markitdownAdapter
|
|
130
|
-
"adapter/markitdown-ts",
|
|
131
|
-
[".pdf", ".docx"],
|
|
132
|
-
[
|
|
133
|
-
"application/pdf",
|
|
134
|
-
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
135
|
-
]
|
|
159
|
+
(await import("./adapters/markitdownTs/adapter")).markitdownAdapter
|
|
136
160
|
);
|
|
137
|
-
const officeparserAdapter =
|
|
161
|
+
const officeparserAdapter = lazyAdapter(
|
|
162
|
+
{
|
|
163
|
+
id: OFFICEPARSER_CONVERTER_ID,
|
|
164
|
+
version: ADAPTER_VERSIONS.officeparser,
|
|
165
|
+
canHandle: officeparserCanHandle,
|
|
166
|
+
},
|
|
138
167
|
async () =>
|
|
139
|
-
(await import("./adapters/officeparser/adapter")).officeparserAdapter
|
|
140
|
-
"adapter/officeparser",
|
|
141
|
-
[".pptx"],
|
|
142
|
-
[
|
|
143
|
-
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
144
|
-
]
|
|
168
|
+
(await import("./adapters/officeparser/adapter")).officeparserAdapter
|
|
145
169
|
);
|
|
146
170
|
const { jsonlAdapter } = await import("./adapters/jsonl/adapter");
|
|
147
171
|
const { transcriptAdapter } = await import("./adapters/transcript/adapter");
|
|
@@ -1,19 +1,111 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ActivationConnectorStatus,
|
|
3
|
+
ActivationStatus,
|
|
4
|
+
ConnectorOmittedSummary,
|
|
5
|
+
} from "./activation-status";
|
|
2
6
|
|
|
3
7
|
const NON_RUNTIME_CODES = new Set([
|
|
4
8
|
"connector_not_configured",
|
|
5
9
|
"target_runtime_unverifiable",
|
|
6
10
|
]);
|
|
7
11
|
|
|
8
|
-
|
|
12
|
+
export type ConnectorProofOutcome = keyof ConnectorOmittedSummary;
|
|
13
|
+
|
|
14
|
+
/** One shared reading of a connector pair, used for listed and unlisted pairs alike. */
|
|
15
|
+
export function classifyConnectorProof({
|
|
16
|
+
code,
|
|
17
|
+
status,
|
|
18
|
+
}: Pick<ActivationConnectorStatus, "code" | "status">): ConnectorProofOutcome {
|
|
19
|
+
if (code !== undefined && NON_RUNTIME_CODES.has(code)) {
|
|
20
|
+
return "notApplicable";
|
|
21
|
+
}
|
|
22
|
+
if (status === "passed" || status === "failed") {
|
|
23
|
+
return status;
|
|
24
|
+
}
|
|
25
|
+
return "incomplete";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function summarizeConnectorProofs(
|
|
29
|
+
connectors: readonly Pick<ActivationConnectorStatus, "code" | "status">[]
|
|
30
|
+
): ConnectorOmittedSummary {
|
|
31
|
+
const summary: ConnectorOmittedSummary = {
|
|
32
|
+
passed: 0,
|
|
33
|
+
failed: 0,
|
|
34
|
+
incomplete: 0,
|
|
35
|
+
notApplicable: 0,
|
|
36
|
+
};
|
|
37
|
+
for (const connector of connectors) {
|
|
38
|
+
summary[classifyConnectorProof(connector)] += 1;
|
|
39
|
+
}
|
|
40
|
+
return summary;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Summary of the pairs past the display cap, or null when some were omitted
|
|
45
|
+
* without an evaluated result (fail closed).
|
|
46
|
+
*/
|
|
47
|
+
export function omittedConnectorSummary(
|
|
48
|
+
activation: ActivationStatus
|
|
49
|
+
): ConnectorOmittedSummary | null {
|
|
50
|
+
const { omitted, projected, total, truncated } =
|
|
51
|
+
activation.connectorProjection;
|
|
52
|
+
if (!truncated) {
|
|
53
|
+
return { passed: 0, failed: 0, incomplete: 0, notApplicable: 0 };
|
|
54
|
+
}
|
|
55
|
+
if (!omitted) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const counted =
|
|
59
|
+
omitted.passed +
|
|
60
|
+
omitted.failed +
|
|
61
|
+
omitted.incomplete +
|
|
62
|
+
omitted.notApplicable;
|
|
63
|
+
return counted === total - projected ? omitted : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Human summary of unlisted pairs, e.g. "108 passed, 1 failed". */
|
|
67
|
+
export function describeConnectorSummary(
|
|
68
|
+
summary: ConnectorOmittedSummary
|
|
69
|
+
): string {
|
|
70
|
+
const parts = [
|
|
71
|
+
summary.passed > 0 ? `${summary.passed} passed` : null,
|
|
72
|
+
summary.failed > 0 ? `${summary.failed} failed` : null,
|
|
73
|
+
summary.incomplete > 0 ? `${summary.incomplete} pending` : null,
|
|
74
|
+
summary.notApplicable > 0
|
|
75
|
+
? `${summary.notApplicable} not configured or not verifiable`
|
|
76
|
+
: null,
|
|
77
|
+
].filter(Boolean);
|
|
78
|
+
return parts.length > 0 ? parts.join(", ") : "none";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Sentence describing the pairs past the display cap, or null when none were omitted. */
|
|
82
|
+
export function describeOmittedConnectorPairs(
|
|
83
|
+
activation: ActivationStatus
|
|
84
|
+
): string | null {
|
|
85
|
+
const { projected, total, truncated } = activation.connectorProjection;
|
|
86
|
+
if (!truncated) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const omitted = total - projected;
|
|
90
|
+
const summary = omittedConnectorSummary(activation);
|
|
91
|
+
return summary
|
|
92
|
+
? `${omitted} more target/collection checks are not listed: ${describeConnectorSummary(summary)}.`
|
|
93
|
+
: `${omitted} target/collection checks were omitted by the bounded status projection; no result is claimed for them.`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* True when every collection/target pair, listed or not, passed or has a
|
|
98
|
+
* non-runtime code. Pairs omitted without an evaluated result fail closed.
|
|
99
|
+
*/
|
|
9
100
|
export function isConnectorActivationComplete(
|
|
10
101
|
activation: ActivationStatus
|
|
11
102
|
): boolean {
|
|
12
|
-
|
|
103
|
+
const omitted = omittedConnectorSummary(activation);
|
|
104
|
+
if (!omitted || omitted.failed > 0 || omitted.incomplete > 0) {
|
|
13
105
|
return false;
|
|
14
106
|
}
|
|
15
|
-
return activation.connectors.every(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
);
|
|
107
|
+
return activation.connectors.every((connector) => {
|
|
108
|
+
const outcome = classifyConnectorProof(connector);
|
|
109
|
+
return outcome === "passed" || outcome === "notApplicable";
|
|
110
|
+
});
|
|
19
111
|
}
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ConnectorVerificationTarget,
|
|
15
15
|
} from "./connector-verifier";
|
|
16
16
|
|
|
17
|
+
import { summarizeConnectorProofs } from "./activation-connector-health";
|
|
17
18
|
import { isEmptyActivationCollection } from "./activation-empty";
|
|
18
19
|
import { createEphemeralActivationProbePlan } from "./activation-probe-plan";
|
|
19
20
|
import { verifyLexicalActivation } from "./activation-verifier";
|
|
@@ -23,7 +24,7 @@ import {
|
|
|
23
24
|
} from "./connector-verifier";
|
|
24
25
|
|
|
25
26
|
const DEFAULT_CONCURRENCY = 4;
|
|
26
|
-
|
|
27
|
+
/** Bounds the listed pairs only; completeness is decided from every pair. */
|
|
27
28
|
const MAX_CONNECTOR_PROJECTIONS = 64;
|
|
28
29
|
const CONNECTOR_CODES = new Set<ActivationVerificationCode>([
|
|
29
30
|
"connector_not_configured",
|
|
@@ -89,9 +90,21 @@ export interface ActivationStatus {
|
|
|
89
90
|
total: number;
|
|
90
91
|
projected: number;
|
|
91
92
|
truncated: boolean;
|
|
93
|
+
/** Outcome counts for the evaluated pairs past the display cap; present when truncated. */
|
|
94
|
+
omitted?: ConnectorOmittedSummary;
|
|
92
95
|
};
|
|
93
96
|
}
|
|
94
97
|
|
|
98
|
+
/** Per-outcome counts of evaluated connector pairs that are not listed. */
|
|
99
|
+
export interface ConnectorOmittedSummary {
|
|
100
|
+
passed: number;
|
|
101
|
+
failed: number;
|
|
102
|
+
/** Pending, or skipped for a runtime reason (for example lexical not ready). */
|
|
103
|
+
incomplete: number;
|
|
104
|
+
/** Not configured, or a skill runtime that cannot be verified. */
|
|
105
|
+
notApplicable: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
95
108
|
export interface ActivationStatusOptions {
|
|
96
109
|
concurrency?: number;
|
|
97
110
|
semantic?: {
|
|
@@ -329,18 +342,22 @@ async function buildConnectorStatuses(
|
|
|
329
342
|
store: StorePort,
|
|
330
343
|
collections: VerifiedCollection[],
|
|
331
344
|
targets: readonly ConnectorVerificationTarget[]
|
|
332
|
-
): Promise<{
|
|
345
|
+
): Promise<{
|
|
346
|
+
items: ActivationConnectorStatus[];
|
|
347
|
+
omitted: ConnectorOmittedSummary;
|
|
348
|
+
total: number;
|
|
349
|
+
}> {
|
|
333
350
|
const sortedTargets = [...targets].sort((a, b) => a.id.localeCompare(b.id));
|
|
334
|
-
const boundedTargets = sortedTargets.slice(0, MAX_CONNECTOR_TARGETS);
|
|
335
351
|
// A collection with no documents gives a connector nothing to prove.
|
|
336
352
|
const provable = collections.filter(
|
|
337
353
|
({ projected }) => !isEmptyActivationCollection(projected)
|
|
338
354
|
);
|
|
339
355
|
const allPairs = provable.flatMap((collection) =>
|
|
340
|
-
|
|
356
|
+
sortedTargets.map((target) => ({ collection, target }))
|
|
341
357
|
);
|
|
342
|
-
|
|
343
|
-
|
|
358
|
+
// Every pair is read from cached receipts only; the cap bounds the listing.
|
|
359
|
+
const evaluated = await mapBounded(
|
|
360
|
+
allPairs,
|
|
344
361
|
DEFAULT_CONCURRENCY,
|
|
345
362
|
async ({ collection, target }) => {
|
|
346
363
|
const fallback = connectorFallback(
|
|
@@ -377,7 +394,13 @@ async function buildConnectorStatuses(
|
|
|
377
394
|
};
|
|
378
395
|
}
|
|
379
396
|
);
|
|
380
|
-
return {
|
|
397
|
+
return {
|
|
398
|
+
items: evaluated.slice(0, MAX_CONNECTOR_PROJECTIONS),
|
|
399
|
+
omitted: summarizeConnectorProofs(
|
|
400
|
+
evaluated.slice(MAX_CONNECTOR_PROJECTIONS)
|
|
401
|
+
),
|
|
402
|
+
total: evaluated.length,
|
|
403
|
+
};
|
|
381
404
|
}
|
|
382
405
|
|
|
383
406
|
async function verifyCoalesced(
|
|
@@ -526,6 +549,9 @@ export async function buildActivationStatus(
|
|
|
526
549
|
total: connectorStatuses.total,
|
|
527
550
|
projected: connectorStatuses.items.length,
|
|
528
551
|
truncated: connectorStatuses.total > connectorStatuses.items.length,
|
|
552
|
+
...(connectorStatuses.total > connectorStatuses.items.length
|
|
553
|
+
? { omitted: connectorStatuses.omitted }
|
|
554
|
+
: {}),
|
|
529
555
|
},
|
|
530
556
|
};
|
|
531
557
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in startup phase trace for diagnosing stalled CLI runs.
|
|
3
|
+
*
|
|
4
|
+
* With `GNO_PHASE_TRACE=1`, each call writes one line to stderr:
|
|
5
|
+
* `gno-phase <ms since process start> <pid> <label>`. The write is
|
|
6
|
+
* synchronous so a line reaches the pipe before a later blocking call (a
|
|
7
|
+
* SQLite busy wait, a native load) can stall the process; the last line a
|
|
8
|
+
* killed process printed names the phase it stalled in.
|
|
9
|
+
*
|
|
10
|
+
* @module src/core/phase-trace
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// node:fs writeSync: Bun has no synchronous stderr write; process.stderr.write
|
|
14
|
+
// to a pipe can still be queued when the event loop blocks.
|
|
15
|
+
import { writeSync } from "node:fs";
|
|
16
|
+
|
|
17
|
+
export const PHASE_TRACE_ENV = "GNO_PHASE_TRACE";
|
|
18
|
+
|
|
19
|
+
const STDERR_FD = 2;
|
|
20
|
+
|
|
21
|
+
const enabled = process.env[PHASE_TRACE_ENV] === "1";
|
|
22
|
+
|
|
23
|
+
/** Record that the process reached `label` (no-op unless tracing is on). */
|
|
24
|
+
export function tracePhase(label: string): void {
|
|
25
|
+
if (!enabled) return;
|
|
26
|
+
try {
|
|
27
|
+
writeSync(
|
|
28
|
+
STDERR_FD,
|
|
29
|
+
`gno-phase ${Math.round(performance.now())} ${process.pid} ${label}\n`
|
|
30
|
+
);
|
|
31
|
+
} catch {
|
|
32
|
+
// A closed stderr must never fail the traced command.
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/core/write-lease.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { dirname, join } from "node:path";
|
|
|
11
11
|
|
|
12
12
|
import { getIndexDbPath } from "../app/constants";
|
|
13
13
|
import { acquireWriteLock } from "./file-lock";
|
|
14
|
+
import { tracePhase } from "./phase-trace";
|
|
14
15
|
|
|
15
16
|
export const DEFAULT_LOCK_WAIT_MS = 120_000;
|
|
16
17
|
export const WRITE_LEASE_BUSY_MESSAGE =
|
|
@@ -149,6 +150,7 @@ export async function acquireCliWriteLease(
|
|
|
149
150
|
options: AcquireCliWriteLeaseOptions
|
|
150
151
|
): Promise<WriteLeaseResult> {
|
|
151
152
|
const lockPath = writeLeasePath(options.dbPath);
|
|
153
|
+
tracePhase("lease: acquiring");
|
|
152
154
|
// A non-finite or out-of-range wait must never loop unbounded (fn-127 review).
|
|
153
155
|
const requestedWaitMs = Number.isFinite(options.waitMs)
|
|
154
156
|
? Math.min(Math.max(0, options.waitMs), MAX_LOCK_WAIT_MS)
|
|
@@ -163,7 +165,9 @@ export async function acquireCliWriteLease(
|
|
|
163
165
|
const sliceMs = options.noWait ? 0 : Math.min(LOCK_SLICE_MS, remaining);
|
|
164
166
|
const handle = await acquireWriteLock(lockPath, sliceMs);
|
|
165
167
|
if (handle) {
|
|
168
|
+
tracePhase("lease: acquired");
|
|
166
169
|
await writeHolderSidecar(lockPath, options.command);
|
|
170
|
+
tracePhase("lease: holder recorded");
|
|
167
171
|
return {
|
|
168
172
|
ok: true,
|
|
169
173
|
release: async () => {
|
|
@@ -213,6 +217,7 @@ export async function withCliWriteLease<T>(
|
|
|
213
217
|
} finally {
|
|
214
218
|
try {
|
|
215
219
|
await result.release();
|
|
220
|
+
tracePhase("lease: released");
|
|
216
221
|
} catch (cause) {
|
|
217
222
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
218
223
|
process.stderr.write(`gno: failed to release write lease: ${message}\n`);
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { runCli } from "./cli/run";
|
|
10
|
+
import { tracePhase } from "./core/phase-trace";
|
|
10
11
|
import { FILE_PROCESSOR_CHILD_ENV } from "./ingestion/file-child-env";
|
|
11
12
|
import { resetModelManager } from "./llm/nodeLlamaCpp/lifecycle";
|
|
12
13
|
import { IMPORT_CHILD_ENV } from "./sessions/import-child-env";
|
|
@@ -50,6 +51,7 @@ async function cleanupAndExit(code: number): Promise<never> {
|
|
|
50
51
|
await resetModelManager().catch(() => {
|
|
51
52
|
// Ignore cleanup errors on exit
|
|
52
53
|
});
|
|
54
|
+
tracePhase("exit: models reset");
|
|
53
55
|
await Promise.all([flushStream(process.stdout), flushStream(process.stderr)]);
|
|
54
56
|
process.exit(code);
|
|
55
57
|
}
|
|
@@ -89,8 +91,12 @@ if (process.env[FILE_PROCESSOR_CHILD_ENV] === "1") {
|
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
// Await module completion so pending piped stdin keeps Windows Bun alive.
|
|
94
|
+
tracePhase("cli: start");
|
|
92
95
|
await runCli(process.argv)
|
|
93
|
-
.then((code) =>
|
|
96
|
+
.then((code) => {
|
|
97
|
+
tracePhase(`cli: done (${code})`);
|
|
98
|
+
return cleanupAndExit(interruptExitCode || code);
|
|
99
|
+
})
|
|
94
100
|
.catch((err) => {
|
|
95
101
|
process.stderr.write(
|
|
96
102
|
`Fatal error: ${err instanceof Error ? err.message : String(err)}\n`
|
|
@@ -37,6 +37,7 @@ import type {
|
|
|
37
37
|
PrepareOutcome,
|
|
38
38
|
} from "./prepare-file";
|
|
39
39
|
|
|
40
|
+
import { tracePhase } from "../core/phase-trace";
|
|
40
41
|
import { isBunfsPath } from "../serve/spa-production-build";
|
|
41
42
|
import { FILE_PROCESSOR_CHILD_ENV } from "./file-child-env";
|
|
42
43
|
import { PREPARE_PHASES } from "./prepare-file";
|
|
@@ -452,7 +453,9 @@ const runOne = async (
|
|
|
452
453
|
}
|
|
453
454
|
// Loading the processor is not the file's time: its clock starts once the
|
|
454
455
|
// processor can take it.
|
|
456
|
+
tracePhase(`processor: awaiting ${active.kind}`);
|
|
455
457
|
const notReady = await awaitReady(active);
|
|
458
|
+
tracePhase(`processor: ${notReady ?? "ready"}`);
|
|
456
459
|
if (notReady) {
|
|
457
460
|
if (processor === active) stopProcessor();
|
|
458
461
|
return { ok: false, error: unavailableFailure(notReady) };
|
package/src/ingestion/sync.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
type DocumentStructureSnapshot,
|
|
44
44
|
} from "../core/change-diff";
|
|
45
45
|
import { enforceCollectionEgress } from "../core/egress-enforcement";
|
|
46
|
+
import { tracePhase } from "../core/phase-trace";
|
|
46
47
|
import { defaultChunker } from "./chunker";
|
|
47
48
|
import { persistChunkLayout, prepareChunking } from "./chunking";
|
|
48
49
|
import {
|
|
@@ -433,6 +434,7 @@ export class SyncService {
|
|
|
433
434
|
"Compiled context is derived material; index original sources instead",
|
|
434
435
|
};
|
|
435
436
|
if (isCompiledContextPath(entry.relPath)) return generatedResult;
|
|
437
|
+
tracePhase(`sync: file ${entry.relPath}`);
|
|
436
438
|
const limits = {
|
|
437
439
|
maxBytes: options.limits?.maxBytes ?? DEFAULT_LIMITS.maxBytes,
|
|
438
440
|
timeoutMs: options.limits?.timeoutMs ?? DEFAULT_LIMITS.timeoutMs,
|
|
@@ -499,6 +501,7 @@ export class SyncService {
|
|
|
499
501
|
extensionMime.mime,
|
|
500
502
|
extensionMime.ext
|
|
501
503
|
);
|
|
504
|
+
tracePhase("sync: record adapter selected");
|
|
502
505
|
const contentTypeRules = options.contentTypeRules ?? [];
|
|
503
506
|
const contentTypeRulesFingerprint =
|
|
504
507
|
options.contentTypeRulesFingerprint ??
|
|
@@ -673,6 +676,7 @@ export class SyncService {
|
|
|
673
676
|
let previousStructure: DocumentStructureSnapshot | null | undefined =
|
|
674
677
|
existing ? undefined : null;
|
|
675
678
|
const previous = await this.readPreviousRevision(store, existing);
|
|
679
|
+
tracePhase("sync: preparing file");
|
|
676
680
|
const prepared = await this.prepare(
|
|
677
681
|
{
|
|
678
682
|
input: {
|
|
@@ -1626,7 +1630,9 @@ export class SyncService {
|
|
|
1626
1630
|
sourceAvailability: availabilityMode,
|
|
1627
1631
|
directoryAvailability,
|
|
1628
1632
|
};
|
|
1633
|
+
tracePhase(`sync: walking ${collection.name}`);
|
|
1629
1634
|
const { entries, skipped } = await this.walker.walk(walkConfig);
|
|
1635
|
+
tracePhase(`sync: walked ${entries.length} entries`);
|
|
1630
1636
|
|
|
1631
1637
|
// Track seen paths for marking inactive
|
|
1632
1638
|
// Only include TOO_LARGE files (they exist but are unprocessable)
|
|
@@ -1945,7 +1951,9 @@ export class SyncService {
|
|
|
1945
1951
|
options: SyncOptions = {}
|
|
1946
1952
|
): Promise<SyncResult> {
|
|
1947
1953
|
const startTime = Date.now();
|
|
1954
|
+
tracePhase("sync: preparing chunking");
|
|
1948
1955
|
const prepared = await prepareChunking(store, this.chunker, options);
|
|
1956
|
+
tracePhase("sync: chunking prepared");
|
|
1949
1957
|
const results: CollectionSyncResult[] = [];
|
|
1950
1958
|
const deferredProjectionOptions: SyncOptions = {
|
|
1951
1959
|
...prepared.options,
|
|
@@ -1961,6 +1969,7 @@ export class SyncService {
|
|
|
1961
1969
|
results.push(result);
|
|
1962
1970
|
}
|
|
1963
1971
|
|
|
1972
|
+
tracePhase("sync: collections done");
|
|
1964
1973
|
let graphRebuild: SyncResult["graphRebuild"];
|
|
1965
1974
|
if (results.length > 0) {
|
|
1966
1975
|
const projectionErrors = await this.projectTypedEdges(store, {
|
|
@@ -1973,6 +1982,7 @@ export class SyncService {
|
|
|
1973
1982
|
results.at(-1)?.errors.push(...projectionErrors);
|
|
1974
1983
|
}
|
|
1975
1984
|
|
|
1985
|
+
tracePhase("sync: typed edges projected");
|
|
1976
1986
|
// Aggregate totals
|
|
1977
1987
|
const totals = results.reduce(
|
|
1978
1988
|
(acc, r) => ({
|
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
import type { ActivationStatus } from "../core/activation-status";
|
|
4
4
|
import type { HealthCheck } from "./status-model";
|
|
5
5
|
|
|
6
|
+
import {
|
|
7
|
+
classifyConnectorProof,
|
|
8
|
+
describeOmittedConnectorPairs,
|
|
9
|
+
omittedConnectorSummary,
|
|
10
|
+
} from "../core/activation-connector-health";
|
|
6
11
|
import { isEmptyActivationCollection } from "../core/activation-empty";
|
|
7
12
|
|
|
8
13
|
function countLabel(count: number, singular: string): string {
|
|
@@ -69,41 +74,46 @@ export function buildConnectorActivationCheck(
|
|
|
69
74
|
activation: ActivationStatus
|
|
70
75
|
): HealthCheck | null {
|
|
71
76
|
const { projected, total, truncated } = activation.connectorProjection;
|
|
72
|
-
const omitted =
|
|
77
|
+
const omitted = omittedConnectorSummary(activation);
|
|
73
78
|
const observed = activation.connectors.filter(
|
|
74
|
-
(
|
|
75
|
-
code !== "connector_not_configured" &&
|
|
76
|
-
code !== "target_runtime_unverifiable"
|
|
79
|
+
(connector) => classifyConnectorProof(connector) !== "notApplicable"
|
|
77
80
|
);
|
|
78
|
-
|
|
81
|
+
const omittedObserved = omitted
|
|
82
|
+
? omitted.passed + omitted.failed + omitted.incomplete
|
|
83
|
+
: 0;
|
|
84
|
+
if (observed.length === 0 && omittedObserved === 0 && omitted) {
|
|
79
85
|
return null;
|
|
80
86
|
}
|
|
81
87
|
const failed = observed.filter(({ status }) => status === "failed");
|
|
82
88
|
const incomplete = observed.filter(({ status }) => status !== "passed");
|
|
89
|
+
const failedCount = failed.length + (omitted?.failed ?? 0);
|
|
90
|
+
const incompleteCount =
|
|
91
|
+
incomplete.length + (omitted?.incomplete ?? 0) + (omitted?.failed ?? 0);
|
|
92
|
+
const passedCount =
|
|
93
|
+
observed.length - incomplete.length + (omitted?.passed ?? 0);
|
|
83
94
|
const first = failed[0] ?? incomplete[0] ?? observed[0];
|
|
84
95
|
const firstDetail = first
|
|
85
96
|
? `${first.target} / ${first.collection}: ${first.status}${first.code ? `/${first.code}` : ""}${first.remediation ? `. ${first.remediation}` : ""}`
|
|
86
97
|
: null;
|
|
87
|
-
const projectionDetail =
|
|
88
|
-
|
|
89
|
-
: null;
|
|
98
|
+
const projectionDetail = describeOmittedConnectorPairs(activation);
|
|
99
|
+
const listing = truncated ? ` (${projected} of ${total} checks listed)` : "";
|
|
90
100
|
return {
|
|
91
101
|
id: "connector-activation",
|
|
92
102
|
title: "Connector proof",
|
|
93
103
|
status:
|
|
94
|
-
|
|
104
|
+
failedCount > 0
|
|
95
105
|
? "error"
|
|
96
|
-
:
|
|
106
|
+
: incompleteCount > 0 || !omitted
|
|
97
107
|
? "warn"
|
|
98
108
|
: "ok",
|
|
99
109
|
summary:
|
|
100
|
-
|
|
101
|
-
? `${countLabel(
|
|
102
|
-
:
|
|
103
|
-
? `${countLabel(
|
|
104
|
-
:
|
|
105
|
-
? `${
|
|
106
|
-
: `${
|
|
110
|
+
failedCount > 0
|
|
111
|
+
? `${countLabel(failedCount, "connector proof")} failed${listing}`
|
|
112
|
+
: incompleteCount > 0
|
|
113
|
+
? `${countLabel(incompleteCount, "connector proof")} incomplete${listing}`
|
|
114
|
+
: omitted
|
|
115
|
+
? `${countLabel(passedCount, "connector proof")} passed${listing}`
|
|
116
|
+
: `${projected} of ${total} connector target/collection checks projected`,
|
|
107
117
|
detail: [projectionDetail, firstDetail].filter(Boolean).join(" "),
|
|
108
118
|
};
|
|
109
119
|
}
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
|
|
10
10
|
import type { AppStatusResponse } from "../../status-model";
|
|
11
11
|
|
|
12
|
+
import {
|
|
13
|
+
describeConnectorSummary,
|
|
14
|
+
omittedConnectorSummary as summarizeOmittedConnectors,
|
|
15
|
+
} from "../../../core/activation-connector-health";
|
|
12
16
|
import { isEmptyActivationCollection } from "../../../core/activation-empty";
|
|
13
17
|
import { buildConnectorActivationCheck } from "../../activation-health";
|
|
14
18
|
import { Button } from "./ui/button";
|
|
@@ -54,6 +58,7 @@ export function BootstrapStatus({
|
|
|
54
58
|
const omittedConnectorCount =
|
|
55
59
|
activation.connectorProjection.total -
|
|
56
60
|
activation.connectorProjection.projected;
|
|
61
|
+
const omittedConnectorSummary = summarizeOmittedConnectors(activation);
|
|
57
62
|
const connectorHealth = buildConnectorActivationCheck(activation);
|
|
58
63
|
const connectorsHealthy =
|
|
59
64
|
connectorHealth === null || connectorHealth.status === "ok";
|
|
@@ -221,6 +226,9 @@ export function BootstrapStatus({
|
|
|
221
226
|
<p className="text-muted-foreground text-xs">
|
|
222
227
|
{omittedConnectorCount} additional target/collection checks
|
|
223
228
|
omitted from this bounded status view
|
|
229
|
+
{omittedConnectorSummary
|
|
230
|
+
? ` (${describeConnectorSummary(omittedConnectorSummary)})`
|
|
231
|
+
: ""}
|
|
224
232
|
</p>
|
|
225
233
|
)}
|
|
226
234
|
</div>
|
|
@@ -145,6 +145,7 @@ import {
|
|
|
145
145
|
type LinkWorkspaceSource,
|
|
146
146
|
} from "../../core/link-workspace";
|
|
147
147
|
import { normalizeWikiName, stripWikiMdExt } from "../../core/links";
|
|
148
|
+
import { tracePhase } from "../../core/phase-trace";
|
|
148
149
|
import {
|
|
149
150
|
TYPED_METADATA_INGEST_VERSION,
|
|
150
151
|
typedMetadataSchema,
|
|
@@ -615,6 +616,7 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
615
616
|
): Promise<StoreResult<MigrationResult>> {
|
|
616
617
|
try {
|
|
617
618
|
this.db = new Database(dbPath, { create: true });
|
|
619
|
+
tracePhase("db: connection open");
|
|
618
620
|
this.shutdownFenced = false;
|
|
619
621
|
this.shutdownDeadline = undefined;
|
|
620
622
|
this.dbPath = dbPath;
|
|
@@ -633,8 +635,10 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
633
635
|
const journalMode = this.db
|
|
634
636
|
.query<{ journal_mode: string }, []>("PRAGMA journal_mode")
|
|
635
637
|
.get()?.journal_mode;
|
|
638
|
+
tracePhase(`db: journal_mode ${journalMode}`);
|
|
636
639
|
if (journalMode?.toLowerCase() !== "wal") {
|
|
637
640
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
641
|
+
tracePhase("db: journal_mode set to wal");
|
|
638
642
|
}
|
|
639
643
|
} catch (cause) {
|
|
640
644
|
if (!isDatabaseLockedError(cause)) {
|
|
@@ -664,7 +668,9 @@ export class SqliteAdapter implements StorePort, SqliteDbProvider {
|
|
|
664
668
|
}
|
|
665
669
|
|
|
666
670
|
// Run migrations
|
|
671
|
+
tracePhase("db: migrations");
|
|
667
672
|
const result = runMigrations(this.db, migrations, ftsTokenizer);
|
|
673
|
+
tracePhase("db: migrations done");
|
|
668
674
|
if (!result.ok) {
|
|
669
675
|
this.db.close();
|
|
670
676
|
this.db = null;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
956ba0ce42d3b72635b61ca710d7adc8599fbdf08f3e32000d979158cac432c6 gno-browser-clipper-v2.8.4.zip
|