sourceloop 0.1.1
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 +401 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +103 -0
- package/dist/commands/attach.js.map +1 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +33 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/chrome.d.ts +2 -0
- package/dist/commands/chrome.js +30 -0
- package/dist/commands/chrome.js.map +1 -0
- package/dist/commands/compose.d.ts +2 -0
- package/dist/commands/compose.js +14 -0
- package/dist/commands/compose.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +15 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/import-latest.d.ts +2 -0
- package/dist/commands/import-latest.js +42 -0
- package/dist/commands/import-latest.js.map +1 -0
- package/dist/commands/ingest.d.ts +2 -0
- package/dist/commands/ingest.js +14 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +24 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/notebook-bind.d.ts +2 -0
- package/dist/commands/notebook-bind.js +39 -0
- package/dist/commands/notebook-bind.js.map +1 -0
- package/dist/commands/notebook-create.d.ts +2 -0
- package/dist/commands/notebook-create.js +39 -0
- package/dist/commands/notebook-create.js.map +1 -0
- package/dist/commands/notebook-import.d.ts +2 -0
- package/dist/commands/notebook-import.js +39 -0
- package/dist/commands/notebook-import.js.map +1 -0
- package/dist/commands/notebook-source.d.ts +2 -0
- package/dist/commands/notebook-source.js +71 -0
- package/dist/commands/notebook-source.js.map +1 -0
- package/dist/commands/plan.d.ts +9 -0
- package/dist/commands/plan.js +59 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +76 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +15 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/topic.d.ts +2 -0
- package/dist/commands/topic.js +53 -0
- package/dist/commands/topic.js.map +1 -0
- package/dist/core/attach/launch-managed-chrome.d.ts +27 -0
- package/dist/core/attach/launch-managed-chrome.js +136 -0
- package/dist/core/attach/launch-managed-chrome.js.map +1 -0
- package/dist/core/attach/manage-targets.d.ts +49 -0
- package/dist/core/attach/manage-targets.js +179 -0
- package/dist/core/attach/manage-targets.js.map +1 -0
- package/dist/core/ingest/frontmatter.d.ts +4 -0
- package/dist/core/ingest/frontmatter.js +30 -0
- package/dist/core/ingest/frontmatter.js.map +1 -0
- package/dist/core/ingest/html-to-markdown.d.ts +5 -0
- package/dist/core/ingest/html-to-markdown.js +53 -0
- package/dist/core/ingest/html-to-markdown.js.map +1 -0
- package/dist/core/ingest/ingest-source.d.ts +11 -0
- package/dist/core/ingest/ingest-source.js +115 -0
- package/dist/core/ingest/ingest-source.js.map +1 -0
- package/dist/core/notebooklm/adapter.d.ts +17 -0
- package/dist/core/notebooklm/adapter.js +2 -0
- package/dist/core/notebooklm/adapter.js.map +1 -0
- package/dist/core/notebooklm/auth.d.ts +30 -0
- package/dist/core/notebooklm/auth.js +105 -0
- package/dist/core/notebooklm/auth.js.map +1 -0
- package/dist/core/notebooklm/browser-agent-adapter.d.ts +21 -0
- package/dist/core/notebooklm/browser-agent-adapter.js +37 -0
- package/dist/core/notebooklm/browser-agent-adapter.js.map +1 -0
- package/dist/core/notebooklm/browser-agent.d.ts +121 -0
- package/dist/core/notebooklm/browser-agent.js +1604 -0
- package/dist/core/notebooklm/browser-agent.js.map +1 -0
- package/dist/core/notebooklm/config.d.ts +20 -0
- package/dist/core/notebooklm/config.js +133 -0
- package/dist/core/notebooklm/config.js.map +1 -0
- package/dist/core/notebooklm/fixture-adapter.d.ts +13 -0
- package/dist/core/notebooklm/fixture-adapter.js +32 -0
- package/dist/core/notebooklm/fixture-adapter.js.map +1 -0
- package/dist/core/notebooklm/response-extraction.d.ts +23 -0
- package/dist/core/notebooklm/response-extraction.js +348 -0
- package/dist/core/notebooklm/response-extraction.js.map +1 -0
- package/dist/core/notebooks/bind-notebook.d.ts +21 -0
- package/dist/core/notebooks/bind-notebook.js +95 -0
- package/dist/core/notebooks/bind-notebook.js.map +1 -0
- package/dist/core/notebooks/manage-managed-notebooks.d.ts +70 -0
- package/dist/core/notebooks/manage-managed-notebooks.js +491 -0
- package/dist/core/notebooks/manage-managed-notebooks.js.map +1 -0
- package/dist/core/notebooks/manage-notebook-source-manifests.d.ts +25 -0
- package/dist/core/notebooks/manage-notebook-source-manifests.js +127 -0
- package/dist/core/notebooks/manage-notebook-source-manifests.js.map +1 -0
- package/dist/core/operator/workspace-operator.d.ts +82 -0
- package/dist/core/operator/workspace-operator.js +610 -0
- package/dist/core/operator/workspace-operator.js.map +1 -0
- package/dist/core/outputs/compose-run.d.ts +11 -0
- package/dist/core/outputs/compose-run.js +98 -0
- package/dist/core/outputs/compose-run.js.map +1 -0
- package/dist/core/runs/load-artifacts.d.ts +14 -0
- package/dist/core/runs/load-artifacts.js +51 -0
- package/dist/core/runs/load-artifacts.js.map +1 -0
- package/dist/core/runs/question-planner.d.ts +20 -0
- package/dist/core/runs/question-planner.js +276 -0
- package/dist/core/runs/question-planner.js.map +1 -0
- package/dist/core/runs/render-run-note.d.ts +13 -0
- package/dist/core/runs/render-run-note.js +111 -0
- package/dist/core/runs/render-run-note.js.map +1 -0
- package/dist/core/runs/run-qa.d.ts +28 -0
- package/dist/core/runs/run-qa.js +393 -0
- package/dist/core/runs/run-qa.js.map +1 -0
- package/dist/core/topics/manage-topics.d.ts +27 -0
- package/dist/core/topics/manage-topics.js +314 -0
- package/dist/core/topics/manage-topics.js.map +1 -0
- package/dist/core/vault/notes.d.ts +29 -0
- package/dist/core/vault/notes.js +147 -0
- package/dist/core/vault/notes.js.map +1 -0
- package/dist/core/vault/paths.d.ts +31 -0
- package/dist/core/vault/paths.js +44 -0
- package/dist/core/vault/paths.js.map +1 -0
- package/dist/core/workspace/bootstrap.d.ts +16 -0
- package/dist/core/workspace/bootstrap.js +443 -0
- package/dist/core/workspace/bootstrap.js.map +1 -0
- package/dist/core/workspace/constants.d.ts +3 -0
- package/dist/core/workspace/constants.js +16 -0
- package/dist/core/workspace/constants.js.map +1 -0
- package/dist/core/workspace/init-workspace.d.ts +15 -0
- package/dist/core/workspace/init-workspace.js +86 -0
- package/dist/core/workspace/init-workspace.js.map +1 -0
- package/dist/core/workspace/load-workspace.d.ts +6 -0
- package/dist/core/workspace/load-workspace.js +51 -0
- package/dist/core/workspace/load-workspace.js.map +1 -0
- package/dist/core/workspace/schema.d.ts +19 -0
- package/dist/core/workspace/schema.js +19 -0
- package/dist/core/workspace/schema.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cli-output.d.ts +2 -0
- package/dist/lib/cli-output.js +7 -0
- package/dist/lib/cli-output.js.map +1 -0
- package/dist/lib/obsidian.d.ts +4 -0
- package/dist/lib/obsidian.js +23 -0
- package/dist/lib/obsidian.js.map +1 -0
- package/dist/lib/slugify.d.ts +1 -0
- package/dist/lib/slugify.js +10 -0
- package/dist/lib/slugify.js.map +1 -0
- package/dist/lib/write-json.d.ts +1 -0
- package/dist/lib/write-json.js +5 -0
- package/dist/lib/write-json.js.map +1 -0
- package/dist/schemas/attach.d.ts +118 -0
- package/dist/schemas/attach.js +33 -0
- package/dist/schemas/attach.js.map +1 -0
- package/dist/schemas/managed-notebook.d.ts +47 -0
- package/dist/schemas/managed-notebook.js +30 -0
- package/dist/schemas/managed-notebook.js.map +1 -0
- package/dist/schemas/notebook-source.d.ts +31 -0
- package/dist/schemas/notebook-source.js +23 -0
- package/dist/schemas/notebook-source.js.map +1 -0
- package/dist/schemas/notebook.d.ts +26 -0
- package/dist/schemas/notebook.js +18 -0
- package/dist/schemas/notebook.js.map +1 -0
- package/dist/schemas/run.d.ts +169 -0
- package/dist/schemas/run.js +80 -0
- package/dist/schemas/run.js.map +1 -0
- package/dist/schemas/source.d.ts +18 -0
- package/dist/schemas/source.js +13 -0
- package/dist/schemas/source.js.map +1 -0
- package/dist/schemas/topic.d.ts +37 -0
- package/dist/schemas/topic.js +25 -0
- package/dist/schemas/topic.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { listChromeAttachTargets } from "../attach/manage-targets.js";
|
|
4
|
+
import { loadWorkspace } from "../workspace/load-workspace.js";
|
|
5
|
+
import { getRunPaths, getVaultPaths } from "../vault/paths.js";
|
|
6
|
+
import { notebookBindingSchema } from "../../schemas/notebook.js";
|
|
7
|
+
import { managedNotebookImportSchema, managedNotebookSetupSchema } from "../../schemas/managed-notebook.js";
|
|
8
|
+
import { notebookSourceManifestSchema } from "../../schemas/notebook-source.js";
|
|
9
|
+
import { runIndexSchema, questionBatchSchema } from "../../schemas/run.js";
|
|
10
|
+
import { sourceDocumentSchema } from "../../schemas/source.js";
|
|
11
|
+
import { listTopics } from "../topics/manage-topics.js";
|
|
12
|
+
export async function buildWorkspaceStatusReport(cwd) {
|
|
13
|
+
const artifacts = await loadWorkspaceArtifacts(cwd);
|
|
14
|
+
const topicSummaries = buildTopicSummaries(artifacts);
|
|
15
|
+
const runSummaries = buildRunSummaries(artifacts);
|
|
16
|
+
const bindingEvidence = buildBindingEvidenceSummaries(artifacts);
|
|
17
|
+
const attachTargetsById = new Map(artifacts.attachTargets.map((target) => [target.id, target]));
|
|
18
|
+
const trustedIsolatedAttachTargetCount = Array.from(attachTargetsById.values()).filter(isTrustedManagedNotebooklmReadyAttachTarget).length;
|
|
19
|
+
const nextActions = recommendNextActions(artifacts, topicSummaries, runSummaries, bindingEvidence);
|
|
20
|
+
return {
|
|
21
|
+
workspaceRoot: artifacts.workspace.rootDir,
|
|
22
|
+
summary: {
|
|
23
|
+
topicCount: artifacts.topics.length,
|
|
24
|
+
notebookBindingCount: artifacts.notebookBindings.length,
|
|
25
|
+
localSourceCount: artifacts.sources.length,
|
|
26
|
+
notebookEvidenceCount: artifacts.notebookSourceManifests.filter((manifest) => artifacts.notebookBindings.some((binding) => binding.id === manifest.notebookBindingId)).length,
|
|
27
|
+
managedNotebookCount: artifacts.managedNotebookSetups.length,
|
|
28
|
+
managedImportCount: artifacts.managedNotebookImports.filter((managedImport) => managedImport.status === "imported" &&
|
|
29
|
+
artifacts.notebookBindings.some((binding) => binding.id === managedImport.notebookBindingId)).length,
|
|
30
|
+
attachTargetCount: artifacts.attachTargets.length,
|
|
31
|
+
trustedIsolatedAttachTargetCount,
|
|
32
|
+
attachIsolation: {
|
|
33
|
+
isolated: countAttachTargetsByIsolation(attachTargetsById, "isolated"),
|
|
34
|
+
unknown: countAttachTargetsByIsolation(attachTargetsById, "unknown"),
|
|
35
|
+
shared: countAttachTargetsByIsolation(attachTargetsById, "shared")
|
|
36
|
+
},
|
|
37
|
+
runCount: artifacts.runs.length,
|
|
38
|
+
plannedRunCount: runSummaries.filter((run) => run.status === "planned").length,
|
|
39
|
+
incompleteRunCount: runSummaries.filter((run) => run.status === "incomplete").length,
|
|
40
|
+
completedRunCount: runSummaries.filter((run) => run.status === "completed").length
|
|
41
|
+
},
|
|
42
|
+
topics: topicSummaries,
|
|
43
|
+
runs: runSummaries,
|
|
44
|
+
nextActions
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function buildDoctorReport(cwd) {
|
|
48
|
+
const artifacts = await loadWorkspaceArtifacts(cwd);
|
|
49
|
+
const findings = [];
|
|
50
|
+
const bindingEvidence = buildBindingEvidenceSummaries(artifacts);
|
|
51
|
+
if (artifacts.topics.length === 0) {
|
|
52
|
+
findings.push({
|
|
53
|
+
severity: "info",
|
|
54
|
+
category: "workspace",
|
|
55
|
+
message: "Workspace has no research topics yet.",
|
|
56
|
+
suggestedCommand: 'sourceloop topic create --name "Your topic"'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const bindingIds = new Set(artifacts.notebookBindings.map((binding) => binding.id));
|
|
60
|
+
const attachTargetIds = new Set(artifacts.attachTargets.map((target) => target.id));
|
|
61
|
+
const attachTargetsById = new Map(artifacts.attachTargets.map((target) => [target.id, target]));
|
|
62
|
+
const trustedIsolatedAttachTargets = artifacts.attachTargets.filter(isTrustedManagedNotebooklmReadyAttachTarget);
|
|
63
|
+
const managedIsolatedUnvalidatedTargets = artifacts.attachTargets.filter(isManagedIsolatedAttachTarget).filter((target) => target.notebooklmReadiness !== "validated");
|
|
64
|
+
if (artifacts.topics.length > 0 && trustedIsolatedAttachTargets.length === 0 && managedIsolatedUnvalidatedTargets.length === 0) {
|
|
65
|
+
findings.push({
|
|
66
|
+
severity: "warning",
|
|
67
|
+
category: "attach",
|
|
68
|
+
message: "No SourceLoop-managed isolated Chrome target is available for NotebookLM automation.",
|
|
69
|
+
suggestedCommand: 'sourceloop chrome launch'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
for (const target of managedIsolatedUnvalidatedTargets) {
|
|
73
|
+
findings.push({
|
|
74
|
+
severity: "warning",
|
|
75
|
+
category: "attach",
|
|
76
|
+
message: `Managed isolated Chrome target ${target.id} has not been validated against NotebookLM yet.`,
|
|
77
|
+
suggestedCommand: `sourceloop attach validate ${target.id}`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
for (const topic of artifacts.topics) {
|
|
81
|
+
const topicBindings = artifacts.notebookBindings.filter((binding) => binding.topicId === topic.id);
|
|
82
|
+
if (topicBindings.length === 0) {
|
|
83
|
+
findings.push({
|
|
84
|
+
severity: "warning",
|
|
85
|
+
category: "topic",
|
|
86
|
+
topicId: topic.id,
|
|
87
|
+
message: `Topic ${topic.id} does not have a notebook binding yet.`,
|
|
88
|
+
suggestedCommand: `sourceloop notebook-bind --name "${topic.name}" --topic-id ${topic.id} --url "https://notebooklm.google.com/notebook/..."`
|
|
89
|
+
});
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const bindingsMissingEvidence = topicBindings.filter((binding) => {
|
|
93
|
+
const summary = bindingEvidence.get(binding.id);
|
|
94
|
+
return !summary?.hasUsableEvidence;
|
|
95
|
+
});
|
|
96
|
+
for (const binding of bindingsMissingEvidence) {
|
|
97
|
+
const summary = bindingEvidence.get(binding.id);
|
|
98
|
+
const needsFirstSource = summary ? needsFirstManagedSourceImport(summary) : false;
|
|
99
|
+
findings.push({
|
|
100
|
+
severity: summary?.hasManagedNotebook ? "warning" : "error",
|
|
101
|
+
category: "evidence",
|
|
102
|
+
topicId: topic.id,
|
|
103
|
+
notebookBindingId: binding.id,
|
|
104
|
+
message: summary?.hasManagedNotebook
|
|
105
|
+
? needsFirstSource
|
|
106
|
+
? `Managed notebook binding ${binding.id} for topic ${topic.id} still needs its first imported source.`
|
|
107
|
+
: `Managed notebook binding ${binding.id} for topic ${topic.id} has no imported evidence yet.`
|
|
108
|
+
: `Notebook binding ${binding.id} for topic ${topic.id} has no aligned local or notebook-backed evidence.`,
|
|
109
|
+
suggestedCommand: summary?.hasManagedNotebook
|
|
110
|
+
? `sourceloop notebook-import --notebook ${binding.id} --url "https://..."`
|
|
111
|
+
: `sourceloop notebook-source declare --topic-id ${topic.id} --notebook ${binding.id} --kind mixed --title "${topic.name} source set"`
|
|
112
|
+
});
|
|
113
|
+
if ((summary?.queuedManagedImportCount ?? 0) > 0) {
|
|
114
|
+
findings.push({
|
|
115
|
+
severity: "info",
|
|
116
|
+
category: "evidence",
|
|
117
|
+
topicId: topic.id,
|
|
118
|
+
notebookBindingId: binding.id,
|
|
119
|
+
message: `Managed notebook binding ${binding.id} has queued imports that are not yet counted as usable evidence.`
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if ((summary?.failedManagedImportCount ?? 0) > 0) {
|
|
123
|
+
findings.push({
|
|
124
|
+
severity: "warning",
|
|
125
|
+
category: "evidence",
|
|
126
|
+
topicId: topic.id,
|
|
127
|
+
notebookBindingId: binding.id,
|
|
128
|
+
message: `Managed notebook binding ${binding.id} has failed imports that need to be retried.`,
|
|
129
|
+
suggestedCommand: `sourceloop notebook-import --notebook ${binding.id} --url "https://..." --force`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const binding of artifacts.notebookBindings) {
|
|
135
|
+
if (binding.attachTargetId && !attachTargetIds.has(binding.attachTargetId)) {
|
|
136
|
+
findings.push({
|
|
137
|
+
severity: "warning",
|
|
138
|
+
category: "attach",
|
|
139
|
+
notebookBindingId: binding.id,
|
|
140
|
+
...(binding.topicId ? { topicId: binding.topicId } : {}),
|
|
141
|
+
message: `Notebook binding ${binding.id} references missing attach target ${binding.attachTargetId}.`,
|
|
142
|
+
suggestedCommand: `sourceloop attach endpoint --name ${binding.attachTargetId.replace(/^attach-/, "")} --endpoint http://127.0.0.1:9222`
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (binding.attachTargetId) {
|
|
146
|
+
const attachTarget = attachTargetsById.get(binding.attachTargetId);
|
|
147
|
+
if (attachTarget && !isTrustedManagedNotebooklmReadyAttachTarget(attachTarget)) {
|
|
148
|
+
findings.push({
|
|
149
|
+
severity: "warning",
|
|
150
|
+
category: "attach",
|
|
151
|
+
notebookBindingId: binding.id,
|
|
152
|
+
...(binding.topicId ? { topicId: binding.topicId } : {}),
|
|
153
|
+
message: attachTarget.profileIsolation === "shared"
|
|
154
|
+
? `Notebook binding ${binding.id} is using a different Chrome than the SourceLoop browser. Ask whether to keep going with that Chrome or switch back to the SourceLoop browser.`
|
|
155
|
+
: attachTarget.profileIsolation === "unknown"
|
|
156
|
+
? `Notebook binding ${binding.id} is using a different Chrome than the SourceLoop browser. Ask whether to keep going with that Chrome or switch back to the SourceLoop browser.`
|
|
157
|
+
: attachTarget.ownership !== "sourceloop_managed"
|
|
158
|
+
? `Notebook binding ${binding.id} is using a different Chrome than the SourceLoop browser. Ask whether to keep going with that Chrome or switch back to the SourceLoop browser.`
|
|
159
|
+
: `Notebook binding ${binding.id} uses managed isolated Chrome attach target ${attachTarget.id}, but it has not been validated against NotebookLM yet.`,
|
|
160
|
+
suggestedCommand: buildAttachSafetyCommand(attachTarget)
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const manifest of artifacts.notebookSourceManifests) {
|
|
166
|
+
if (!bindingIds.has(manifest.notebookBindingId)) {
|
|
167
|
+
findings.push({
|
|
168
|
+
severity: "warning",
|
|
169
|
+
category: "binding",
|
|
170
|
+
topicId: manifest.topicId,
|
|
171
|
+
notebookBindingId: manifest.notebookBindingId,
|
|
172
|
+
message: `Notebook source manifest ${manifest.id} points to missing notebook binding ${manifest.notebookBindingId}.`
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const managedImport of artifacts.managedNotebookImports) {
|
|
177
|
+
if (!bindingIds.has(managedImport.notebookBindingId)) {
|
|
178
|
+
findings.push({
|
|
179
|
+
severity: "warning",
|
|
180
|
+
category: "binding",
|
|
181
|
+
topicId: managedImport.topicId,
|
|
182
|
+
notebookBindingId: managedImport.notebookBindingId,
|
|
183
|
+
message: `Managed notebook import ${managedImport.id} points to missing notebook binding ${managedImport.notebookBindingId}.`
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const run of artifacts.runs) {
|
|
188
|
+
if (run.status === "incomplete") {
|
|
189
|
+
findings.push({
|
|
190
|
+
severity: "warning",
|
|
191
|
+
category: "run",
|
|
192
|
+
runId: run.id,
|
|
193
|
+
...(run.topicId ? { topicId: run.topicId } : {}),
|
|
194
|
+
notebookBindingId: run.notebookBindingId,
|
|
195
|
+
message: `Run ${run.id} is incomplete and can be resumed from the remaining questions.`,
|
|
196
|
+
suggestedCommand: `sourceloop run ${run.id}`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (run.status === "planned" && !hasUsableAttachTarget(run.notebookBindingId, artifacts.notebookBindings, attachTargetIds)) {
|
|
200
|
+
findings.push({
|
|
201
|
+
severity: "warning",
|
|
202
|
+
category: "attach",
|
|
203
|
+
runId: run.id,
|
|
204
|
+
...(run.topicId ? { topicId: run.topicId } : {}),
|
|
205
|
+
notebookBindingId: run.notebookBindingId,
|
|
206
|
+
message: `Run ${run.id} does not have a usable attach target on its notebook binding.`,
|
|
207
|
+
suggestedCommand: `sourceloop run ${run.id} --attach-target <target-id>`
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const errorCount = findings.filter((finding) => finding.severity === "error").length;
|
|
212
|
+
const warningCount = findings.filter((finding) => finding.severity === "warning").length;
|
|
213
|
+
const infoCount = findings.filter((finding) => finding.severity === "info").length;
|
|
214
|
+
return {
|
|
215
|
+
workspaceRoot: artifacts.workspace.rootDir,
|
|
216
|
+
healthy: errorCount === 0 && warningCount === 0,
|
|
217
|
+
summary: {
|
|
218
|
+
errorCount,
|
|
219
|
+
warningCount,
|
|
220
|
+
infoCount
|
|
221
|
+
},
|
|
222
|
+
findings
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
export function formatWorkspaceStatusReport(report) {
|
|
226
|
+
if (report.summary.topicCount === 0) {
|
|
227
|
+
const firstAction = report.nextActions[0];
|
|
228
|
+
return [
|
|
229
|
+
`Workspace: ${report.workspaceRoot}`,
|
|
230
|
+
"",
|
|
231
|
+
"No active research setup yet.",
|
|
232
|
+
...(firstAction ? ["", "Next Action", `- ${firstAction.message}`, ` ${firstAction.command}`] : [])
|
|
233
|
+
].join("\n");
|
|
234
|
+
}
|
|
235
|
+
const topicLines = report.topics.length === 0
|
|
236
|
+
? ["- none"]
|
|
237
|
+
: report.topics.map((topic) => `- ${topic.id} [${topic.status}] notebooks:${topic.notebookBindingCount} evidence:${topic.localSourceCount + topic.notebookEvidenceCount + topic.managedNotebookImportCount} runs:${topic.runCount}`);
|
|
238
|
+
const runLines = report.runs.length === 0
|
|
239
|
+
? ["- none yet"]
|
|
240
|
+
: report.runs
|
|
241
|
+
.filter((run) => run.status === "planned" || run.status === "incomplete" || run.status === "running")
|
|
242
|
+
.map((run) => `- ${run.id} [${run.status}] completed:${run.completedQuestionCount}/${run.totalQuestionCount}${run.failedQuestionId ? ` failed:${run.failedQuestionId}` : ""}`);
|
|
243
|
+
const nextActionLines = report.nextActions.length === 0
|
|
244
|
+
? ["- none"]
|
|
245
|
+
: report.nextActions.map((action) => `- ${action.message}\n ${action.command}`);
|
|
246
|
+
return [
|
|
247
|
+
`Workspace: ${report.workspaceRoot}`,
|
|
248
|
+
"",
|
|
249
|
+
"Summary",
|
|
250
|
+
`- Topics: ${report.summary.topicCount}`,
|
|
251
|
+
`- Notebook Bindings: ${report.summary.notebookBindingCount}`,
|
|
252
|
+
`- Evidence: ${report.summary.localSourceCount + report.summary.notebookEvidenceCount + report.summary.managedImportCount}`,
|
|
253
|
+
`- Attach Targets: ${report.summary.attachTargetCount} (${report.summary.trustedIsolatedAttachTargetCount} trusted isolated, ${report.summary.attachIsolation.isolated} isolated, ${report.summary.attachIsolation.unknown} unknown, ${report.summary.attachIsolation.shared} shared)`,
|
|
254
|
+
`- Runs: ${report.summary.runCount} (${report.summary.incompleteRunCount} incomplete, ${report.summary.completedRunCount} completed)`,
|
|
255
|
+
"",
|
|
256
|
+
"Topics",
|
|
257
|
+
...topicLines,
|
|
258
|
+
"",
|
|
259
|
+
"Open Runs",
|
|
260
|
+
...runLines,
|
|
261
|
+
"",
|
|
262
|
+
"Next Actions",
|
|
263
|
+
...nextActionLines
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
266
|
+
export function formatDoctorReport(report) {
|
|
267
|
+
if (report.findings.length === 0) {
|
|
268
|
+
return [`Workspace: ${report.workspaceRoot}`, "", "Doctor found no workflow blockers."].join("\n");
|
|
269
|
+
}
|
|
270
|
+
const findingLines = report.findings.flatMap((finding) => [
|
|
271
|
+
`- [${finding.severity}][${finding.category}] ${finding.message}`,
|
|
272
|
+
...(finding.suggestedCommand ? [` ${finding.suggestedCommand}`] : [])
|
|
273
|
+
]);
|
|
274
|
+
return [
|
|
275
|
+
`Workspace: ${report.workspaceRoot}`,
|
|
276
|
+
"",
|
|
277
|
+
`Doctor Findings: ${report.summary.errorCount} error(s), ${report.summary.warningCount} warning(s), ${report.summary.infoCount} info`,
|
|
278
|
+
...findingLines
|
|
279
|
+
].join("\n");
|
|
280
|
+
}
|
|
281
|
+
async function loadWorkspaceArtifacts(cwd) {
|
|
282
|
+
const workspace = await loadWorkspace(cwd);
|
|
283
|
+
const vault = getVaultPaths(workspace);
|
|
284
|
+
const [topics, notebookBindings, sources, notebookSourceManifests, managedNotebookSetups, managedNotebookImports, runs, attachTargets] = await Promise.all([
|
|
285
|
+
listTopics(workspace.rootDir),
|
|
286
|
+
readJsonDirectory(vault.notebooksDir, notebookBindingSchema),
|
|
287
|
+
readJsonDirectory(vault.sourcesDir, sourceDocumentSchema),
|
|
288
|
+
readJsonDirectory(vault.notebookSourcesDir, notebookSourceManifestSchema),
|
|
289
|
+
readJsonDirectory(vault.notebookSetupsDir, managedNotebookSetupSchema),
|
|
290
|
+
readJsonDirectory(vault.notebookImportsDir, managedNotebookImportSchema),
|
|
291
|
+
readJsonDirectory(vault.runsDir, runIndexSchema, "index.json"),
|
|
292
|
+
listChromeAttachTargets(workspace.rootDir)
|
|
293
|
+
]);
|
|
294
|
+
const questionBatches = new Map();
|
|
295
|
+
for (const run of runs) {
|
|
296
|
+
const batchPath = getRunPaths(workspace, run.id).questionsJsonPath;
|
|
297
|
+
try {
|
|
298
|
+
const raw = await readFile(batchPath, "utf8");
|
|
299
|
+
questionBatches.set(run.id, questionBatchSchema.parse(JSON.parse(raw)));
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
// allow status/doctor to proceed even if a run folder is partially broken
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
workspace,
|
|
307
|
+
topics,
|
|
308
|
+
notebookBindings,
|
|
309
|
+
sources,
|
|
310
|
+
notebookSourceManifests,
|
|
311
|
+
managedNotebookSetups,
|
|
312
|
+
managedNotebookImports,
|
|
313
|
+
runs,
|
|
314
|
+
questionBatches,
|
|
315
|
+
attachTargets
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function buildTopicSummaries(artifacts) {
|
|
319
|
+
const bindingIds = new Set(artifacts.notebookBindings.map((binding) => binding.id));
|
|
320
|
+
return artifacts.topics
|
|
321
|
+
.map((topic) => {
|
|
322
|
+
const topicBindings = artifacts.notebookBindings.filter((binding) => binding.topicId === topic.id);
|
|
323
|
+
const localSourceCount = artifacts.sources.filter((source) => source.topicId === topic.id).length;
|
|
324
|
+
const notebookEvidenceCount = artifacts.notebookSourceManifests.filter((manifest) => manifest.topicId === topic.id && bindingIds.has(manifest.notebookBindingId)).length;
|
|
325
|
+
const managedNotebookImportCount = artifacts.managedNotebookImports.filter((managedImport) => managedImport.topicId === topic.id &&
|
|
326
|
+
managedImport.status === "imported" &&
|
|
327
|
+
topicBindings.some((binding) => isManagedNotebookImportCompatibleWithBinding(managedImport, binding, artifacts.managedNotebookSetups))).length;
|
|
328
|
+
const runs = artifacts.runs.filter((run) => run.topicId === topic.id);
|
|
329
|
+
return {
|
|
330
|
+
id: topic.id,
|
|
331
|
+
name: topic.name,
|
|
332
|
+
status: deriveTopicStatus({
|
|
333
|
+
localSourceCount,
|
|
334
|
+
notebookEvidenceCount,
|
|
335
|
+
managedNotebookImportCount,
|
|
336
|
+
notebookBindingCount: topicBindings.length,
|
|
337
|
+
runs
|
|
338
|
+
}),
|
|
339
|
+
notebookBindingCount: topicBindings.length,
|
|
340
|
+
localSourceCount,
|
|
341
|
+
notebookEvidenceCount,
|
|
342
|
+
managedNotebookImportCount,
|
|
343
|
+
runCount: runs.length,
|
|
344
|
+
plannedRunCount: runs.filter((run) => run.status === "planned").length,
|
|
345
|
+
incompleteRunCount: runs.filter((run) => run.status === "incomplete").length,
|
|
346
|
+
completedRunCount: runs.filter((run) => run.status === "completed").length
|
|
347
|
+
};
|
|
348
|
+
})
|
|
349
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
350
|
+
}
|
|
351
|
+
function buildBindingEvidenceSummaries(artifacts) {
|
|
352
|
+
return new Map(artifacts.notebookBindings.map((binding) => {
|
|
353
|
+
const localSourceCount = artifacts.sources.filter((source) => source.topicId === binding.topicId).length;
|
|
354
|
+
const alignedNotebookEvidenceCount = artifacts.notebookSourceManifests.filter((manifest) => manifest.topicId === binding.topicId && manifest.notebookBindingId === binding.id).length;
|
|
355
|
+
const managedImports = artifacts.managedNotebookImports.filter((managedImport) => managedImport.topicId === binding.topicId &&
|
|
356
|
+
isManagedNotebookImportCompatibleWithBinding(managedImport, binding, artifacts.managedNotebookSetups));
|
|
357
|
+
const importedManagedEvidenceCount = managedImports.filter((managedImport) => managedImport.status === "imported").length;
|
|
358
|
+
const queuedManagedImportCount = managedImports.filter((managedImport) => managedImport.status === "queued").length;
|
|
359
|
+
const failedManagedImportCount = managedImports.filter((managedImport) => managedImport.status === "failed").length;
|
|
360
|
+
const hasManagedNotebook = artifacts.managedNotebookSetups.some((setup) => isManagedNotebookSetupCompatibleWithBinding(setup, binding));
|
|
361
|
+
return [
|
|
362
|
+
binding.id,
|
|
363
|
+
{
|
|
364
|
+
notebookBindingId: binding.id,
|
|
365
|
+
...(binding.topicId ? { topicId: binding.topicId } : {}),
|
|
366
|
+
hasManagedNotebook,
|
|
367
|
+
localSourceCount,
|
|
368
|
+
alignedNotebookEvidenceCount,
|
|
369
|
+
importedManagedEvidenceCount,
|
|
370
|
+
queuedManagedImportCount,
|
|
371
|
+
failedManagedImportCount,
|
|
372
|
+
hasUsableEvidence: localSourceCount + alignedNotebookEvidenceCount + importedManagedEvidenceCount > 0
|
|
373
|
+
}
|
|
374
|
+
];
|
|
375
|
+
}));
|
|
376
|
+
}
|
|
377
|
+
function isManagedNotebookSetupCompatibleWithBinding(setup, binding) {
|
|
378
|
+
if (setup.notebookBindingId === binding.id) {
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
return Boolean(setup.remoteNotebookId && binding.remoteNotebookId && setup.remoteNotebookId === binding.remoteNotebookId);
|
|
382
|
+
}
|
|
383
|
+
function isManagedNotebookImportCompatibleWithBinding(managedImport, binding, setups) {
|
|
384
|
+
if (managedImport.notebookBindingId === binding.id) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
if (!binding.remoteNotebookId) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
const setup = setups.find((candidate) => candidate.id === managedImport.managedNotebookSetupId);
|
|
391
|
+
return Boolean(setup?.remoteNotebookId && setup.remoteNotebookId === binding.remoteNotebookId);
|
|
392
|
+
}
|
|
393
|
+
function buildRunSummaries(artifacts) {
|
|
394
|
+
return artifacts.runs
|
|
395
|
+
.map((run) => {
|
|
396
|
+
const batch = artifacts.questionBatches.get(run.id);
|
|
397
|
+
return {
|
|
398
|
+
id: run.id,
|
|
399
|
+
topic: run.topic,
|
|
400
|
+
...(run.topicId ? { topicId: run.topicId } : {}),
|
|
401
|
+
notebookBindingId: run.notebookBindingId,
|
|
402
|
+
status: run.status,
|
|
403
|
+
completedQuestionCount: run.completedQuestionIds.length,
|
|
404
|
+
totalQuestionCount: batch?.questions.length ?? run.completedQuestionIds.length,
|
|
405
|
+
...(run.failedQuestionId ? { failedQuestionId: run.failedQuestionId } : {}),
|
|
406
|
+
...(run.executionMode ? { executionMode: run.executionMode } : {})
|
|
407
|
+
};
|
|
408
|
+
})
|
|
409
|
+
.sort((left, right) => right.id.localeCompare(left.id));
|
|
410
|
+
}
|
|
411
|
+
function recommendNextActions(artifacts, topics, runs, bindingEvidence) {
|
|
412
|
+
const actions = [];
|
|
413
|
+
const attachTargetsById = new Map(artifacts.attachTargets.map((target) => [target.id, target]));
|
|
414
|
+
const hasTrustedIsolatedTarget = artifacts.attachTargets.some(isTrustedManagedNotebooklmReadyAttachTarget);
|
|
415
|
+
const managedUnvalidatedTarget = artifacts.attachTargets.find((target) => isManagedIsolatedAttachTarget(target) && target.notebooklmReadiness !== "validated");
|
|
416
|
+
if (artifacts.topics.length === 0) {
|
|
417
|
+
return [
|
|
418
|
+
{
|
|
419
|
+
kind: "create_topic",
|
|
420
|
+
message: "Create your first research topic.",
|
|
421
|
+
command: 'sourceloop topic create --name "Your topic"'
|
|
422
|
+
}
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
if (!hasTrustedIsolatedTarget && !managedUnvalidatedTarget) {
|
|
426
|
+
actions.push({
|
|
427
|
+
kind: "launch_isolated_browser",
|
|
428
|
+
message: "Launch a managed isolated Chrome target before more NotebookLM work.",
|
|
429
|
+
command: "sourceloop chrome launch"
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (!hasTrustedIsolatedTarget && managedUnvalidatedTarget) {
|
|
433
|
+
actions.push({
|
|
434
|
+
kind: "validate_attach",
|
|
435
|
+
message: `Validate managed Chrome target ${managedUnvalidatedTarget.id} against NotebookLM before more work.`,
|
|
436
|
+
command: `sourceloop attach validate ${managedUnvalidatedTarget.id}`
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
for (const run of runs.filter((candidate) => candidate.status === "incomplete")) {
|
|
440
|
+
actions.push({
|
|
441
|
+
kind: "resume_run",
|
|
442
|
+
runId: run.id,
|
|
443
|
+
...(run.topicId ? { topicId: run.topicId } : {}),
|
|
444
|
+
notebookBindingId: run.notebookBindingId,
|
|
445
|
+
message: `Resume incomplete run ${run.id}.`,
|
|
446
|
+
command: `sourceloop run ${run.id}`
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
for (const topic of topics) {
|
|
450
|
+
if (topic.notebookBindingCount === 0) {
|
|
451
|
+
actions.push({
|
|
452
|
+
kind: "bind_notebook",
|
|
453
|
+
topicId: topic.id,
|
|
454
|
+
message: `Bind a NotebookLM notebook for topic ${topic.id}.`,
|
|
455
|
+
command: `sourceloop notebook-bind --name "${topic.name}" --topic-id ${topic.id} --url "https://notebooklm.google.com/notebook/..."`
|
|
456
|
+
});
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const riskyBinding = artifacts.notebookBindings.find((candidate) => {
|
|
460
|
+
if (candidate.topicId !== topic.id || !candidate.attachTargetId) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
const attachTarget = attachTargetsById.get(candidate.attachTargetId);
|
|
464
|
+
return Boolean(attachTarget && !isTrustedManagedNotebooklmReadyAttachTarget(attachTarget));
|
|
465
|
+
});
|
|
466
|
+
if (riskyBinding?.attachTargetId) {
|
|
467
|
+
const attachTarget = attachTargetsById.get(riskyBinding.attachTargetId);
|
|
468
|
+
if (attachTarget) {
|
|
469
|
+
actions.push({
|
|
470
|
+
kind: "launch_isolated_browser",
|
|
471
|
+
topicId: topic.id,
|
|
472
|
+
notebookBindingId: riskyBinding.id,
|
|
473
|
+
message: attachTarget.profileIsolation === "shared"
|
|
474
|
+
? `Ask whether to keep going with the current Chrome or switch back to the SourceLoop browser before continuing topic ${topic.id}.`
|
|
475
|
+
: attachTarget.profileIsolation === "unknown"
|
|
476
|
+
? `Ask whether to keep going with the current Chrome or switch back to the SourceLoop browser before continuing topic ${topic.id}.`
|
|
477
|
+
: attachTarget.ownership !== "sourceloop_managed"
|
|
478
|
+
? `Ask whether to keep going with the current Chrome or switch back to the SourceLoop browser before continuing topic ${topic.id}.`
|
|
479
|
+
: `Validate managed isolated Chrome attach target ${attachTarget.id} against NotebookLM before continuing work for topic ${topic.id}.`,
|
|
480
|
+
command: buildAttachSafetyCommand(attachTarget)
|
|
481
|
+
});
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const missingEvidenceBinding = artifacts.notebookBindings.find((candidate) => candidate.topicId === topic.id && !bindingEvidence.get(candidate.id)?.hasUsableEvidence);
|
|
486
|
+
if (missingEvidenceBinding) {
|
|
487
|
+
const summary = bindingEvidence.get(missingEvidenceBinding.id);
|
|
488
|
+
const needsFirstSource = summary ? needsFirstManagedSourceImport(summary) : false;
|
|
489
|
+
actions.push({
|
|
490
|
+
kind: summary?.hasManagedNotebook ? "import_managed_source" : "declare_evidence",
|
|
491
|
+
topicId: topic.id,
|
|
492
|
+
notebookBindingId: missingEvidenceBinding.id,
|
|
493
|
+
message: summary?.hasManagedNotebook
|
|
494
|
+
? needsFirstSource
|
|
495
|
+
? `Import the first source into managed notebook ${missingEvidenceBinding.id}.`
|
|
496
|
+
: `Import sources into managed notebook ${missingEvidenceBinding.id}.`
|
|
497
|
+
: `Declare evidence for topic ${topic.id}.`,
|
|
498
|
+
command: summary?.hasManagedNotebook
|
|
499
|
+
? `sourceloop notebook-import --notebook ${missingEvidenceBinding.id} --url "https://..."`
|
|
500
|
+
: `sourceloop notebook-source declare --topic-id ${topic.id} --notebook ${missingEvidenceBinding.id} --kind mixed --title "${topic.name} source set"`
|
|
501
|
+
});
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (topic.runCount === 0) {
|
|
505
|
+
const hasUsableBinding = artifacts.notebookBindings.some((binding) => binding.topicId === topic.id && bindingEvidence.get(binding.id)?.hasUsableEvidence);
|
|
506
|
+
if (!hasUsableBinding) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
actions.push({
|
|
510
|
+
kind: "plan_questions",
|
|
511
|
+
topicId: topic.id,
|
|
512
|
+
message: `Plan questions for topic ${topic.id}.`,
|
|
513
|
+
command: `sourceloop plan ${topic.id}`
|
|
514
|
+
});
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
const plannedRun = runs.find((run) => run.topicId === topic.id && run.status === "planned");
|
|
518
|
+
if (plannedRun && bindingEvidence.get(plannedRun.notebookBindingId)?.hasUsableEvidence) {
|
|
519
|
+
actions.push({
|
|
520
|
+
kind: "run_planned",
|
|
521
|
+
topicId: topic.id,
|
|
522
|
+
runId: plannedRun.id,
|
|
523
|
+
notebookBindingId: plannedRun.notebookBindingId,
|
|
524
|
+
message: `Run planned batch ${plannedRun.id}.`,
|
|
525
|
+
command: `sourceloop run ${plannedRun.id}`
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return dedupeActions(actions).slice(0, 6);
|
|
530
|
+
}
|
|
531
|
+
function dedupeActions(actions) {
|
|
532
|
+
const seen = new Set();
|
|
533
|
+
return actions.filter((action) => {
|
|
534
|
+
const key = `${action.kind}:${action.topicId ?? ""}:${action.runId ?? ""}:${action.notebookBindingId ?? ""}`;
|
|
535
|
+
if (seen.has(key)) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
seen.add(key);
|
|
539
|
+
return true;
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
function needsFirstManagedSourceImport(summary) {
|
|
543
|
+
return (summary.hasManagedNotebook &&
|
|
544
|
+
summary.importedManagedEvidenceCount === 0 &&
|
|
545
|
+
summary.queuedManagedImportCount === 0 &&
|
|
546
|
+
summary.failedManagedImportCount === 0 &&
|
|
547
|
+
summary.alignedNotebookEvidenceCount === 0);
|
|
548
|
+
}
|
|
549
|
+
function countAttachTargetsByIsolation(attachTargetsById, profileIsolation) {
|
|
550
|
+
return Array.from(attachTargetsById.values()).filter((target) => target.profileIsolation === profileIsolation).length;
|
|
551
|
+
}
|
|
552
|
+
function deriveTopicStatus(input) {
|
|
553
|
+
if (input.runs.some((run) => run.completedQuestionIds.length > 0 ||
|
|
554
|
+
run.status === "completed" ||
|
|
555
|
+
run.status === "incomplete")) {
|
|
556
|
+
return "researched";
|
|
557
|
+
}
|
|
558
|
+
if (input.localSourceCount + input.notebookEvidenceCount + input.managedNotebookImportCount > 0 && input.notebookBindingCount > 0) {
|
|
559
|
+
return "ready_for_planning";
|
|
560
|
+
}
|
|
561
|
+
if (input.localSourceCount + input.notebookEvidenceCount + input.managedNotebookImportCount > 0 || input.notebookBindingCount > 0) {
|
|
562
|
+
return "collecting_sources";
|
|
563
|
+
}
|
|
564
|
+
return "initialized";
|
|
565
|
+
}
|
|
566
|
+
function hasUsableAttachTarget(notebookBindingId, bindings, attachTargetIds) {
|
|
567
|
+
const binding = bindings.find((candidate) => candidate.id === notebookBindingId);
|
|
568
|
+
if (!binding?.attachTargetId) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
return attachTargetIds.has(binding.attachTargetId);
|
|
572
|
+
}
|
|
573
|
+
function buildAttachSafetyCommand(target) {
|
|
574
|
+
if (isManagedIsolatedAttachTarget(target) && target.notebooklmReadiness !== "validated") {
|
|
575
|
+
return `sourceloop attach validate ${target.id}`;
|
|
576
|
+
}
|
|
577
|
+
return `Ask the user: keep going with the current Chrome, or switch to the SourceLoop browser with 'sourceloop chrome launch --name "${target.name}" --force'?`;
|
|
578
|
+
}
|
|
579
|
+
function isManagedIsolatedAttachTarget(target) {
|
|
580
|
+
return target.profileIsolation === "isolated" && target.ownership === "sourceloop_managed";
|
|
581
|
+
}
|
|
582
|
+
function isTrustedManagedNotebooklmReadyAttachTarget(target) {
|
|
583
|
+
return isManagedIsolatedAttachTarget(target) && target.notebooklmReadiness === "validated";
|
|
584
|
+
}
|
|
585
|
+
async function readJsonDirectory(directory, schema, nestedJsonFile) {
|
|
586
|
+
try {
|
|
587
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
588
|
+
const filePaths = entries.flatMap((entry) => {
|
|
589
|
+
if (entry.isDirectory()) {
|
|
590
|
+
return nestedJsonFile ? [path.join(directory, entry.name, nestedJsonFile)] : [];
|
|
591
|
+
}
|
|
592
|
+
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
593
|
+
return [path.join(directory, entry.name)];
|
|
594
|
+
}
|
|
595
|
+
return [];
|
|
596
|
+
});
|
|
597
|
+
const raw = await Promise.all(filePaths.map((filePath) => readFile(filePath, "utf8")));
|
|
598
|
+
return raw.map((value) => schema.parse(JSON.parse(value)));
|
|
599
|
+
}
|
|
600
|
+
catch (error) {
|
|
601
|
+
if (isMissingDirectoryError(error)) {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
function isMissingDirectoryError(error) {
|
|
608
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
609
|
+
}
|
|
610
|
+
//# sourceMappingURL=workspace-operator.js.map
|