presubmit 0.1.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/LICENSE +202 -0
- package/README.md +51 -0
- package/THIRD_PARTY_NOTICES.md +24 -0
- package/apps/web/dist/assets/FileCode-CZZH52o7.js +5 -0
- package/apps/web/dist/assets/FileCode-DovDO7Jh.css +1 -0
- package/apps/web/dist/assets/SettingsPage-CRvOwdjt.js +4 -0
- package/apps/web/dist/assets/SettingsPage-UbYSCwJj.css +1 -0
- package/apps/web/dist/assets/TerminalPanel-0sw-J6YS.css +1 -0
- package/apps/web/dist/assets/TerminalPanel-BqUjPWav.js +16 -0
- package/apps/web/dist/assets/index-BhqNNFja.js +54 -0
- package/apps/web/dist/assets/index-DD8VXy8H.css +1 -0
- package/apps/web/dist/assets/presubmit-mark-BBQ19Fmr.svg +7 -0
- package/apps/web/dist/assets/presubmit-mark-busy-CwBC-7on.svg +8 -0
- package/apps/web/dist/assets/react-vendor-wiHys0m2.js +9 -0
- package/apps/web/dist/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/apps/web/dist/index.html +6 -0
- package/apps/web/dist/service-worker.js +11 -0
- package/dist/apps/server/src/agent-builder.js +204 -0
- package/dist/apps/server/src/attach.js +163 -0
- package/dist/apps/server/src/attachments.js +86 -0
- package/dist/apps/server/src/browser-access.js +131 -0
- package/dist/apps/server/src/check-provenance.js +95 -0
- package/dist/apps/server/src/check-supervisor.js +112 -0
- package/dist/apps/server/src/checks.js +402 -0
- package/dist/apps/server/src/child-collaboration-guidance.js +2 -0
- package/dist/apps/server/src/child-sessions.js +191 -0
- package/dist/apps/server/src/cli.js +162 -0
- package/dist/apps/server/src/conversation-search.js +12 -0
- package/dist/apps/server/src/coordinator-messages.js +72 -0
- package/dist/apps/server/src/dev-auth.js +54 -0
- package/dist/apps/server/src/dev-server.js +201 -0
- package/dist/apps/server/src/dev-worker.js +48 -0
- package/dist/apps/server/src/diagnostics.js +105 -0
- package/dist/apps/server/src/fast-mode.js +8 -0
- package/dist/apps/server/src/folder-picker.js +54 -0
- package/dist/apps/server/src/git-blobs.js +62 -0
- package/dist/apps/server/src/git-handoff.js +127 -0
- package/dist/apps/server/src/history-sources.js +59 -0
- package/dist/apps/server/src/hub.js +159 -0
- package/dist/apps/server/src/internal-transport.js +38 -0
- package/dist/apps/server/src/local-embeddings.js +70 -0
- package/dist/apps/server/src/ollama-setup.js +56 -0
- package/dist/apps/server/src/planning.js +30 -0
- package/dist/apps/server/src/project-agents.js +276 -0
- package/dist/apps/server/src/project-memory.js +94 -0
- package/dist/apps/server/src/projects.js +106 -0
- package/dist/apps/server/src/queued-launch.js +13 -0
- package/dist/apps/server/src/reply-search.js +172 -0
- package/dist/apps/server/src/review-context.js +60 -0
- package/dist/apps/server/src/review-refresh.js +14 -0
- package/dist/apps/server/src/reviewers.js +75 -0
- package/dist/apps/server/src/search-settings.js +22 -0
- package/dist/apps/server/src/semantic-history.js +186 -0
- package/dist/apps/server/src/server.js +4338 -0
- package/dist/apps/server/src/session-changes.js +181 -0
- package/dist/apps/server/src/session-router-settings.js +198 -0
- package/dist/apps/server/src/session-workspaces.js +135 -0
- package/dist/apps/server/src/sessions.js +579 -0
- package/dist/apps/server/src/shared-auth.js +70 -0
- package/dist/apps/server/src/shell-evaluator-settings.js +53 -0
- package/dist/apps/server/src/store.js +203 -0
- package/dist/apps/server/src/tailscale-process.js +115 -0
- package/dist/apps/server/src/tailscale-serve-worker.js +33 -0
- package/dist/apps/server/src/tailscale-serve.js +276 -0
- package/dist/apps/server/src/task-capacity.js +92 -0
- package/dist/apps/server/src/task-graph.js +190 -0
- package/dist/apps/server/src/task-logs.js +74 -0
- package/dist/apps/server/src/task-numbers.js +25 -0
- package/dist/apps/server/src/task-orchestrator.js +1088 -0
- package/dist/apps/server/src/terminal.js +182 -0
- package/dist/apps/server/src/terminals.js +80 -0
- package/dist/apps/server/src/tool-safety.js +2 -0
- package/dist/apps/server/src/workspace-lock.js +140 -0
- package/dist/apps/server/src/workspace-watcher.js +65 -0
- package/dist/apps/server/src/workspace.js +517 -0
- package/dist/packages/agent-pi/src/agent-builder-tool.js +32 -0
- package/dist/packages/agent-pi/src/agent-capabilities.js +44 -0
- package/dist/packages/agent-pi/src/ask-user.js +86 -0
- package/dist/packages/agent-pi/src/auth.js +510 -0
- package/dist/packages/agent-pi/src/check-tools.js +72 -0
- package/dist/packages/agent-pi/src/command-policy.js +246 -0
- package/dist/packages/agent-pi/src/coordinator-tools.js +122 -0
- package/dist/packages/agent-pi/src/fast-mode.js +67 -0
- package/dist/packages/agent-pi/src/file-tools.js +163 -0
- package/dist/packages/agent-pi/src/history-tool.js +40 -0
- package/dist/packages/agent-pi/src/history.js +34 -0
- package/dist/packages/agent-pi/src/index.js +177 -0
- package/dist/packages/agent-pi/src/ipc.js +314 -0
- package/dist/packages/agent-pi/src/jev-session-router.js +408 -0
- package/dist/packages/agent-pi/src/jev-tools.js +71 -0
- package/dist/packages/agent-pi/src/local-model.js +277 -0
- package/dist/packages/agent-pi/src/memory-tools.js +119 -0
- package/dist/packages/agent-pi/src/model.js +25 -0
- package/dist/packages/agent-pi/src/planning.js +206 -0
- package/dist/packages/agent-pi/src/resource-loader.js +23 -0
- package/dist/packages/agent-pi/src/review-tools.js +158 -0
- package/dist/packages/agent-pi/src/review-transition.js +73 -0
- package/dist/packages/agent-pi/src/review.js +40 -0
- package/dist/packages/agent-pi/src/routing.js +27 -0
- package/dist/packages/agent-pi/src/session-setup.js +118 -0
- package/dist/packages/agent-pi/src/shell-context.js +51 -0
- package/dist/packages/agent-pi/src/shell-policy.js +281 -0
- package/dist/packages/agent-pi/src/shell-safety.js +205 -0
- package/dist/packages/agent-pi/src/shell.js +121 -0
- package/dist/packages/agent-pi/src/snapshot-permissions.js +12 -0
- package/dist/packages/agent-pi/src/spike.js +41 -0
- package/dist/packages/agent-pi/src/task-tools.js +53 -0
- package/dist/packages/agent-pi/src/types.js +1 -0
- package/dist/packages/agent-pi/src/usage.js +30 -0
- package/dist/packages/agent-pi/src/worker.js +602 -0
- package/dist/packages/protocol/src/activity-summary.js +77 -0
- package/dist/packages/protocol/src/agents.js +301 -0
- package/dist/packages/protocol/src/browser-access.js +1 -0
- package/dist/packages/protocol/src/checks.js +40 -0
- package/dist/packages/protocol/src/child-sessions.js +28 -0
- package/dist/packages/protocol/src/command-policy.js +3 -0
- package/dist/packages/protocol/src/coordinator.js +135 -0
- package/dist/packages/protocol/src/delegation.js +40 -0
- package/dist/packages/protocol/src/git-diff.js +71 -0
- package/dist/packages/protocol/src/git-handoff.js +1 -0
- package/dist/packages/protocol/src/index.js +1 -0
- package/dist/packages/protocol/src/memory.js +46 -0
- package/dist/packages/protocol/src/metrics.js +70 -0
- package/dist/packages/protocol/src/planning.js +138 -0
- package/dist/packages/protocol/src/retrieval.js +36 -0
- package/dist/packages/protocol/src/reviewers.js +17 -0
- package/dist/packages/protocol/src/search.js +16 -0
- package/dist/packages/protocol/src/session-changes.js +29 -0
- package/dist/packages/protocol/src/session-router.js +123 -0
- package/dist/packages/protocol/src/shell-evaluator.js +43 -0
- package/dist/packages/protocol/src/task-graph.js +1 -0
- package/dist/packages/protocol/src/token-usage.js +17 -0
- package/dist/packages/protocol/src/user-questions.js +106 -0
- package/package.json +71 -0
- package/vendor/lancedb/NODEJS_THIRD_PARTY_LICENSES.md +668 -0
- package/vendor/lancedb/RUST_THIRD_PARTY_LICENSES.html +14607 -0
- package/vendor/lancedb/dist/arrow.d.ts +296 -0
- package/vendor/lancedb/dist/arrow.js +1145 -0
- package/vendor/lancedb/dist/arrow_type.d.ts +9 -0
- package/vendor/lancedb/dist/arrow_type.js +29 -0
- package/vendor/lancedb/dist/connection.d.ts +508 -0
- package/vendor/lancedb/dist/connection.js +298 -0
- package/vendor/lancedb/dist/embedding/embedding_function.d.ts +103 -0
- package/vendor/lancedb/dist/embedding/embedding_function.js +192 -0
- package/vendor/lancedb/dist/embedding/index.d.ts +37 -0
- package/vendor/lancedb/dist/embedding/index.js +108 -0
- package/vendor/lancedb/dist/embedding/openai.d.ts +16 -0
- package/vendor/lancedb/dist/embedding/openai.js +81 -0
- package/vendor/lancedb/dist/embedding/registry.d.ts +94 -0
- package/vendor/lancedb/dist/embedding/registry.js +221 -0
- package/vendor/lancedb/dist/embedding/transformers.d.ts +36 -0
- package/vendor/lancedb/dist/embedding/transformers.js +110 -0
- package/vendor/lancedb/dist/header.d.ts +162 -0
- package/vendor/lancedb/dist/header.js +217 -0
- package/vendor/lancedb/dist/index.d.ts +246 -0
- package/vendor/lancedb/dist/index.js +176 -0
- package/vendor/lancedb/dist/indices.d.ts +721 -0
- package/vendor/lancedb/dist/indices.js +166 -0
- package/vendor/lancedb/dist/materialized_view.d.ts +69 -0
- package/vendor/lancedb/dist/materialized_view.js +112 -0
- package/vendor/lancedb/dist/merge.d.ts +104 -0
- package/vendor/lancedb/dist/merge.js +120 -0
- package/vendor/lancedb/dist/native.d.ts +1193 -0
- package/vendor/lancedb/dist/native.js +688 -0
- package/vendor/lancedb/dist/oauth.d.ts +66 -0
- package/vendor/lancedb/dist/oauth.js +15 -0
- package/vendor/lancedb/dist/otel.d.ts +26 -0
- package/vendor/lancedb/dist/otel.js +114 -0
- package/vendor/lancedb/dist/permutation.d.ts +143 -0
- package/vendor/lancedb/dist/permutation.js +184 -0
- package/vendor/lancedb/dist/query.d.ts +663 -0
- package/vendor/lancedb/dist/query.js +966 -0
- package/vendor/lancedb/dist/rerankers/index.d.ts +5 -0
- package/vendor/lancedb/dist/rerankers/index.js +19 -0
- package/vendor/lancedb/dist/rerankers/rrf.d.ts +14 -0
- package/vendor/lancedb/dist/rerankers/rrf.js +28 -0
- package/vendor/lancedb/dist/sanitize.d.ts +32 -0
- package/vendor/lancedb/dist/sanitize.js +544 -0
- package/vendor/lancedb/dist/scannable.d.ts +92 -0
- package/vendor/lancedb/dist/scannable.js +200 -0
- package/vendor/lancedb/dist/schema.d.ts +16 -0
- package/vendor/lancedb/dist/schema.js +387 -0
- package/vendor/lancedb/dist/table.d.ts +1069 -0
- package/vendor/lancedb/dist/table.js +498 -0
- package/vendor/lancedb/dist/util.d.ts +14 -0
- package/vendor/lancedb/dist/util.js +77 -0
- package/vendor/lancedb/license_header.txt +2 -0
- package/vendor/lancedb/package.json +56 -0
- package/vendor/transformers/LICENSE +202 -0
- package/vendor/transformers/dist/transformers.cjs +30844 -0
- package/vendor/transformers/dist/transformers.mjs +31404 -0
- package/vendor/transformers/package.json +55 -0
- package/vendor/transformers/types/backends/onnx.d.ts +30 -0
- package/vendor/transformers/types/configs.d.ts +85 -0
- package/vendor/transformers/types/env.d.ts +110 -0
- package/vendor/transformers/types/generation/configuration_utils.d.ts +320 -0
- package/vendor/transformers/types/generation/logits_process.d.ts +354 -0
- package/vendor/transformers/types/generation/logits_sampler.d.ts +51 -0
- package/vendor/transformers/types/generation/parameters.d.ts +47 -0
- package/vendor/transformers/types/generation/stopping_criteria.d.ts +81 -0
- package/vendor/transformers/types/generation/streamers.d.ts +81 -0
- package/vendor/transformers/types/models/whisper/common_whisper.d.ts +8 -0
- package/vendor/transformers/types/models/whisper/generation_whisper.d.ts +76 -0
- package/vendor/transformers/types/models.d.ts +3684 -0
- package/vendor/transformers/types/ops/registry.d.ts +11 -0
- package/vendor/transformers/types/pipelines.d.ts +2402 -0
- package/vendor/transformers/types/processors.d.ts +924 -0
- package/vendor/transformers/types/tokenizers.d.ts +990 -0
- package/vendor/transformers/types/transformers.d.ts +13 -0
- package/vendor/transformers/types/utils/audio.d.ts +130 -0
- package/vendor/transformers/types/utils/constants.d.ts +2 -0
- package/vendor/transformers/types/utils/core.d.ts +98 -0
- package/vendor/transformers/types/utils/data-structures.d.ts +236 -0
- package/vendor/transformers/types/utils/devices.d.ts +18 -0
- package/vendor/transformers/types/utils/dtypes.d.ts +19 -0
- package/vendor/transformers/types/utils/generic.d.ts +11 -0
- package/vendor/transformers/types/utils/hub.d.ts +152 -0
- package/vendor/transformers/types/utils/image.d.ts +119 -0
- package/vendor/transformers/types/utils/maths.d.ts +280 -0
- package/vendor/transformers/types/utils/tensor.d.ts +418 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { childCollaborationGuidance } from './child-collaboration-guidance.js';
|
|
3
|
+
import { childSessionLimits, } from '../../../packages/protocol/src/child-sessions.js';
|
|
4
|
+
/** Project-owned durable intents; per-child receipts provide cross-store exactly-once acceptance. */
|
|
5
|
+
export function createChildSessions(options) {
|
|
6
|
+
const intents = options.store.get('ordinaryChildStarts') ?? [];
|
|
7
|
+
const pending = new Map();
|
|
8
|
+
const reports = options.store.get('ordinaryChildReports') ?? [];
|
|
9
|
+
const saveReports = () => options.store.put('ordinaryChildReports', reports);
|
|
10
|
+
function enqueueReport(parentId, id, text) {
|
|
11
|
+
if (reports.some((r) => r.id === id))
|
|
12
|
+
return;
|
|
13
|
+
reports.push({ parentId, id, text, delivered: false });
|
|
14
|
+
saveReports();
|
|
15
|
+
}
|
|
16
|
+
const owner = (id) => `child:${options.store.directory}:${id}`;
|
|
17
|
+
const save = () => options.store.put('ordinaryChildStarts', intents);
|
|
18
|
+
function lineage(sourceId) {
|
|
19
|
+
const summaries = options.summaries();
|
|
20
|
+
let current = summaries.find((s) => s.id === sourceId);
|
|
21
|
+
let depth = 0;
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
while (current) {
|
|
24
|
+
if (current.assignment)
|
|
25
|
+
throw new Error('Approved task attempts cannot start ordinary children. Ask the coordinator.');
|
|
26
|
+
if (seen.has(current.id))
|
|
27
|
+
throw new Error('Invalid session lineage.');
|
|
28
|
+
seen.add(current.id);
|
|
29
|
+
if (!current.parentSessionId)
|
|
30
|
+
return { rootId: current.id, depth };
|
|
31
|
+
depth++;
|
|
32
|
+
current = summaries.find((s) => s.id === current.parentSessionId);
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Parent session is unavailable.');
|
|
35
|
+
}
|
|
36
|
+
function acquire(id) {
|
|
37
|
+
const intent = intents.find((i) => i.launch.destinationId === id);
|
|
38
|
+
if (!intent)
|
|
39
|
+
return;
|
|
40
|
+
if (!options.capacity.acquire(owner(id), `${options.store.directory}:${intent.sourceId}`))
|
|
41
|
+
throw new Error(`Child capacity is full (${options.capacity.limit} active children per parent session). Wait for a child or task to finish, then retry the same requestKey.`);
|
|
42
|
+
}
|
|
43
|
+
async function start(sourceId, key, fingerprint, launch, validate) {
|
|
44
|
+
validate();
|
|
45
|
+
const { rootId, depth } = lineage(sourceId);
|
|
46
|
+
let intent = intents.find((i) => i.sourceId === sourceId && i.key === key);
|
|
47
|
+
if (intent && intent.fingerprint !== fingerprint)
|
|
48
|
+
throw new Error('requestKey already belongs to different child instructions. Reuse the original request or choose a new key for genuinely new work.');
|
|
49
|
+
if (intent?.accepted)
|
|
50
|
+
return intent.launch.destinationId;
|
|
51
|
+
const pendingKey = `${sourceId}:${key}`;
|
|
52
|
+
if (pending.has(pendingKey))
|
|
53
|
+
return pending.get(pendingKey);
|
|
54
|
+
if (!intent) {
|
|
55
|
+
if (depth >= childSessionLimits.depth)
|
|
56
|
+
throw new Error('Child nesting limit reached (maximum depth 2). Complete this work here or ask the direct parent.');
|
|
57
|
+
if (intents.filter((i) => i.rootId === rootId).length >= childSessionLimits.totalSpawns)
|
|
58
|
+
throw new Error('Root tree spawn budget exhausted (8 total children, including failed starts). Continue in an existing session.');
|
|
59
|
+
// Reserve capacity before writing a new durable spawn; limits never consume a spawn.
|
|
60
|
+
launch.destinationId = randomUUID();
|
|
61
|
+
if (!options.capacity.acquire(owner(launch.destinationId), `${options.store.directory}:${sourceId}`))
|
|
62
|
+
throw new Error(`Child capacity is full (${options.capacity.limit} active children per parent session). Wait and retry the same requestKey.`);
|
|
63
|
+
intent = {
|
|
64
|
+
sourceId,
|
|
65
|
+
rootId,
|
|
66
|
+
key,
|
|
67
|
+
fingerprint,
|
|
68
|
+
launch,
|
|
69
|
+
accepted: false,
|
|
70
|
+
error: 'Handoff was interrupted before acceptance. Retry the original requestKey.',
|
|
71
|
+
};
|
|
72
|
+
intents.push(intent);
|
|
73
|
+
save();
|
|
74
|
+
}
|
|
75
|
+
const saved = intent;
|
|
76
|
+
const operation = (async () => {
|
|
77
|
+
acquire(saved.launch.destinationId);
|
|
78
|
+
try {
|
|
79
|
+
validate();
|
|
80
|
+
await options.launch(sourceId, saved.launch, validate);
|
|
81
|
+
saved.accepted = true;
|
|
82
|
+
saved.error = undefined;
|
|
83
|
+
save();
|
|
84
|
+
return saved.launch.destinationId;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
saved.error = error instanceof Error ? error.message : String(error);
|
|
88
|
+
save();
|
|
89
|
+
options.capacity.release(owner(saved.launch.destinationId));
|
|
90
|
+
throw new Error(`Child ${saved.launch.destinationId} handoff incomplete: ${saved.error} Retry the same requestKey, not a new start.`);
|
|
91
|
+
}
|
|
92
|
+
})();
|
|
93
|
+
pending.set(pendingKey, operation);
|
|
94
|
+
try {
|
|
95
|
+
return await operation;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
pending.delete(pendingKey);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let ticking = false;
|
|
102
|
+
async function reconcile() {
|
|
103
|
+
if (ticking)
|
|
104
|
+
return;
|
|
105
|
+
ticking = true;
|
|
106
|
+
try {
|
|
107
|
+
for (const intent of intents) {
|
|
108
|
+
const id = intent.launch.destinationId;
|
|
109
|
+
if (pending.has(`${intent.sourceId}:${intent.key}`))
|
|
110
|
+
continue;
|
|
111
|
+
const state = options.state(id);
|
|
112
|
+
if (!state?.runs.some((r) => r.status === 'running') &&
|
|
113
|
+
!(state?.queuedMessages?.length && !state.queuePaused))
|
|
114
|
+
options.capacity.release(owner(id));
|
|
115
|
+
// Reviewer progress/findings are collected by Reviews, never injected as coding work.
|
|
116
|
+
if (intent.launch.reviewer)
|
|
117
|
+
continue;
|
|
118
|
+
for (const run of state?.runs ?? []) {
|
|
119
|
+
if (run.status === 'running')
|
|
120
|
+
continue;
|
|
121
|
+
const text = childReportText(state.events, run.id);
|
|
122
|
+
const number = options
|
|
123
|
+
.summaries()
|
|
124
|
+
.find((session) => session.id === id)?.childSessionNumber;
|
|
125
|
+
enqueueReport(intent.sourceId, `child-result:${id}:${run.id}`, `Child session ${id}${number ? ` [T${number}]` : ''} (${run.status}) — ${options.url?.(id) ?? `/s/${id}`}\n${childCollaborationGuidance}\nAttributed child report:\n${text || run.error || 'No final response was produced. Inspect the child session for details.'}`);
|
|
126
|
+
}
|
|
127
|
+
if (!state?.runs.length && intent.error)
|
|
128
|
+
enqueueReport(intent.sourceId, `child-start-failure:${id}`, `Child session ${id} could not start: ${intent.error}. Retry the original requestKey (${intent.key}) to complete the handoff without duplicating work.`);
|
|
129
|
+
}
|
|
130
|
+
for (const report of reports) {
|
|
131
|
+
if (report.delivered)
|
|
132
|
+
continue;
|
|
133
|
+
try {
|
|
134
|
+
await options.report(report.parentId, report.id, report.text);
|
|
135
|
+
report.delivered = true;
|
|
136
|
+
saveReports();
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
/* A full, stopped or unavailable parent retains the durable outbox entry. */
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
ticking = false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function list(sourceId) {
|
|
148
|
+
return options
|
|
149
|
+
.summaries()
|
|
150
|
+
.filter((s) => s.parentSessionId === sourceId)
|
|
151
|
+
.map((s) => ({
|
|
152
|
+
sessionId: s.id,
|
|
153
|
+
displayId: s.childSessionNumber ? `T${s.childSessionNumber}` : undefined,
|
|
154
|
+
parentSessionId: sourceId,
|
|
155
|
+
title: s.title,
|
|
156
|
+
status: s.status,
|
|
157
|
+
url: options.url?.(s.id) ?? `/s/${s.id}`,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
start,
|
|
162
|
+
acquire,
|
|
163
|
+
reconcile,
|
|
164
|
+
list,
|
|
165
|
+
async close() {
|
|
166
|
+
await Promise.allSettled(pending.values());
|
|
167
|
+
for (const i of intents)
|
|
168
|
+
options.capacity.release(owner(i.launch.destinationId));
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Text events are streaming deltas; only separate narrative blocks across other events. */
|
|
173
|
+
export function childReportText(events, runId) {
|
|
174
|
+
let text = '';
|
|
175
|
+
let separated = false;
|
|
176
|
+
for (const event of events) {
|
|
177
|
+
if (event.runId !== runId)
|
|
178
|
+
continue;
|
|
179
|
+
if (event.type !== 'text') {
|
|
180
|
+
separated = true;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (!event.text)
|
|
184
|
+
continue;
|
|
185
|
+
if (separated && text)
|
|
186
|
+
text += '\n\n';
|
|
187
|
+
text += event.text;
|
|
188
|
+
separated = false;
|
|
189
|
+
}
|
|
190
|
+
return text.slice(-12000);
|
|
191
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startDiagnostics } from './diagnostics.js';
|
|
3
|
+
import { resolve, join, dirname } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { mkdir, realpath } from 'node:fs/promises';
|
|
9
|
+
import { attachOptions, followAttachment } from './attach.js';
|
|
10
|
+
import { acquireHub, hubAddress } from './hub.js';
|
|
11
|
+
import { tailscaleOrigin } from './browser-access.js';
|
|
12
|
+
import { startServer } from './server.js';
|
|
13
|
+
import { sharedAuthPath } from './shared-auth.js';
|
|
14
|
+
import { discoverWorkspace } from './workspace.js';
|
|
15
|
+
import { WorkspaceAlreadyRunningError } from './workspace-lock.js';
|
|
16
|
+
async function main() {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const dev = process.env.PRESUBMIT_DEV === '1';
|
|
19
|
+
if (args.includes('--help')) {
|
|
20
|
+
console.log('Usage: presubmit [repository] [--port 4317] [--data-dir path] [--tailscale https://machine.tailnet.ts.net]\npresubmit attach <session-id|session-url> [--project id] [--json] [--after seq]\nStart Tailscale Serve from Settings → Devices; the device address is discovered and saved automatically. --tailscale optionally overrides the HTTPS origin or port. Re-run with the same URL for a fresh remote access link.\nConnect OpenAI in the browser (ChatGPT sign-in or API key), or set OPENAI_API_KEY on the server. Human review and shared drafts work without a model.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (args[0] === 'attach') {
|
|
24
|
+
const options = attachOptions(args.slice(1));
|
|
25
|
+
const home = await realpath(resolve(process.env.PRESUBMIT_HOME ?? join(homedir(), '.local', 'share', 'presubmit')));
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const stop = () => controller.abort();
|
|
28
|
+
const outputError = (error) => {
|
|
29
|
+
stop();
|
|
30
|
+
if (error.code !== 'EPIPE')
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
};
|
|
33
|
+
process.once('SIGINT', stop);
|
|
34
|
+
process.once('SIGTERM', stop);
|
|
35
|
+
process.stdout.on('error', outputError);
|
|
36
|
+
try {
|
|
37
|
+
await followAttachment(hubAddress(home), options, process.stdout, controller.signal, (message) => console.error(message));
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
process.removeListener('SIGINT', stop);
|
|
41
|
+
process.removeListener('SIGTERM', stop);
|
|
42
|
+
process.stdout.removeListener('error', outputError);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let cwd = process.cwd(), port = 4317, tailscale, dataDir = process.env.PRESUBMIT_DATA_DIR;
|
|
47
|
+
for (let i = 0; i < args.length; i++) {
|
|
48
|
+
if (args[i] === '--port')
|
|
49
|
+
port = Number(args[++i]);
|
|
50
|
+
else if (args[i] === '--data-dir')
|
|
51
|
+
dataDir = resolve(args[++i]);
|
|
52
|
+
else if (args[i] === '--tailscale')
|
|
53
|
+
tailscale = tailscaleOrigin(args[++i] ?? '');
|
|
54
|
+
else if (args[i].startsWith('--'))
|
|
55
|
+
throw new Error('Unknown option ' + args[i]);
|
|
56
|
+
else
|
|
57
|
+
cwd = resolve(args[i]);
|
|
58
|
+
}
|
|
59
|
+
if (dev && process.env.PRESUBMIT_DEV_PORT)
|
|
60
|
+
port = Number(process.env.PRESUBMIT_DEV_PORT);
|
|
61
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535)
|
|
62
|
+
throw new Error('Invalid port');
|
|
63
|
+
cwd = await discoverWorkspace(cwd);
|
|
64
|
+
let presubmitHome = resolve(process.env.PRESUBMIT_HOME ?? join(homedir(), '.local', 'share', 'presubmit'));
|
|
65
|
+
await mkdir(presubmitHome, { recursive: true, mode: 0o700 });
|
|
66
|
+
presubmitHome = await realpath(presubmitHome);
|
|
67
|
+
dataDir ??= join(presubmitHome, createHash('sha256').update(cwd).digest('hex').slice(0, 20));
|
|
68
|
+
let root = dirname(fileURLToPath(import.meta.url));
|
|
69
|
+
while (!existsSync(join(root, dev ? 'apps/web/index.html' : 'apps/web/dist/index.html')) &&
|
|
70
|
+
dirname(root) !== root)
|
|
71
|
+
root = dirname(root);
|
|
72
|
+
const webDir = join(root, 'apps/web/dist');
|
|
73
|
+
if (!dev && !existsSync(join(webDir, 'index.html')))
|
|
74
|
+
throw new Error('Browser assets missing. Run npm run build first.');
|
|
75
|
+
const hub = await acquireHub(presubmitHome, cwd, tailscale);
|
|
76
|
+
if (hub.existingUrl) {
|
|
77
|
+
console.log(`\nPresubmit is already running for this workspace.\n\nOpen in your browser:\n${hub.existingUrl}\n`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
let app;
|
|
81
|
+
try {
|
|
82
|
+
const authPath = await sharedAuthPath(presubmitHome, dataDir);
|
|
83
|
+
try {
|
|
84
|
+
app = await startServer({
|
|
85
|
+
cwd,
|
|
86
|
+
dataDir,
|
|
87
|
+
webDir,
|
|
88
|
+
port,
|
|
89
|
+
authPath,
|
|
90
|
+
dev,
|
|
91
|
+
tailscaleOrigin: tailscale,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code === 'EADDRINUSE' && port === 4317)
|
|
96
|
+
app = await startServer({
|
|
97
|
+
cwd,
|
|
98
|
+
dataDir,
|
|
99
|
+
webDir,
|
|
100
|
+
port: 0,
|
|
101
|
+
authPath,
|
|
102
|
+
dev,
|
|
103
|
+
tailscaleOrigin: tailscale,
|
|
104
|
+
});
|
|
105
|
+
else
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
await hub.listen(app.origin, (path, remote) => app.openProject(path, remote), app.readAttachEvents);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
await app?.close();
|
|
112
|
+
await hub.close?.();
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
if (!app.getState().configured)
|
|
116
|
+
console.log('Model setup: Choose Connect OpenAI in the browser.');
|
|
117
|
+
console.log(`\nPresubmit · ${cwd}\n\nOpen this access link in your browser:\n${app.url}\n\nKeep this process running. Ctrl+C stops the server.\n`);
|
|
118
|
+
if (tailscale) {
|
|
119
|
+
const httpsPort = new URL(tailscale).port || '443';
|
|
120
|
+
console.log(`Tailscale access link (one use, expires in 10 minutes):\n${app.tailscaleUrl()}\n\nStart Serve in Settings → Devices, or run it in another terminal:\ntailscale serve --https=${httpsPort} ${app.origin}\n\nThe link works once Serve is running for ${tailscale}. Settings → Devices also offers automatic startup with Presubmit.\n`);
|
|
121
|
+
}
|
|
122
|
+
const stopDiagnostics = startDiagnostics('host');
|
|
123
|
+
let stopping = false;
|
|
124
|
+
let restartRequested = false;
|
|
125
|
+
let check;
|
|
126
|
+
const stop = async (code) => {
|
|
127
|
+
if (stopping)
|
|
128
|
+
return;
|
|
129
|
+
stopping = true;
|
|
130
|
+
clearInterval(check);
|
|
131
|
+
stopDiagnostics();
|
|
132
|
+
hub.beginShutdown?.();
|
|
133
|
+
await app.close();
|
|
134
|
+
await hub.close?.();
|
|
135
|
+
process.exit(code);
|
|
136
|
+
};
|
|
137
|
+
if (dev && process.send) {
|
|
138
|
+
process.on('message', (message) => {
|
|
139
|
+
if (message?.type !== 'restart' || restartRequested)
|
|
140
|
+
return;
|
|
141
|
+
restartRequested = true;
|
|
142
|
+
if (app.hasActiveRuns())
|
|
143
|
+
console.log('Server changes detected. Reloading when active sessions finish.');
|
|
144
|
+
check = setInterval(() => {
|
|
145
|
+
if (!app.hasActiveRuns())
|
|
146
|
+
void stop(75);
|
|
147
|
+
}, 200);
|
|
148
|
+
check.unref();
|
|
149
|
+
});
|
|
150
|
+
process.send({ type: 'ready', port: Number(new URL(app.origin).port) });
|
|
151
|
+
}
|
|
152
|
+
for (const signal of ['SIGINT', 'SIGTERM'])
|
|
153
|
+
process.once(signal, () => void stop(0));
|
|
154
|
+
}
|
|
155
|
+
main().catch((e) => {
|
|
156
|
+
if (e instanceof WorkspaceAlreadyRunningError && e.url) {
|
|
157
|
+
console.log(`\nPresubmit is already running for this workspace.\n\nOpen in your browser:\n${e.url}\n`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
console.error('Presubmit:', e.message);
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { searchExcerpt, searchPattern, } from '../../../packages/protocol/src/search.js';
|
|
2
|
+
/** Check in-memory prompts first, then the persistent index of saved reply text. */
|
|
3
|
+
export function searchConversation(store, runs, query) {
|
|
4
|
+
const pattern = searchPattern(query);
|
|
5
|
+
for (let i = runs.length - 1; i >= 0; i--) {
|
|
6
|
+
const run = runs[i];
|
|
7
|
+
const excerpt = searchExcerpt(run.prompt, pattern);
|
|
8
|
+
if (excerpt)
|
|
9
|
+
return { kind: 'prompt', excerpt, runId: run.id };
|
|
10
|
+
}
|
|
11
|
+
return store.replySearch.search(new Set(runs.map((run) => run.id)), query, pattern);
|
|
12
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { parseCoordinatorRequest, } from '../../../packages/protocol/src/coordinator.js';
|
|
3
|
+
/** Capability-scoped adapter for supported launches and follow-ups; never shell approvals. */
|
|
4
|
+
export async function coordinatorRequest(options) {
|
|
5
|
+
const { state, bridge, validate } = options;
|
|
6
|
+
validate();
|
|
7
|
+
if (!options.requestId.trim() || options.requestId.length > 128)
|
|
8
|
+
throw new Error('Invalid coordinator request ID.');
|
|
9
|
+
const request = parseCoordinatorRequest(options.request);
|
|
10
|
+
// Only uploads already sent in this conversation can be forwarded, not arbitrary project IDs.
|
|
11
|
+
const attachments = [
|
|
12
|
+
...new Map(state.runs.flatMap((run) => run.attachments ?? []).map((a) => [a.id, a])).values(),
|
|
13
|
+
].slice(-100);
|
|
14
|
+
let launch;
|
|
15
|
+
if (request.action === 'launch_tasks') {
|
|
16
|
+
launch = (await bridge.command({
|
|
17
|
+
id: `agent-launch:${createHash('sha256').update(request.requestKey).digest('hex')}`,
|
|
18
|
+
type: 'delegation_agent_launch',
|
|
19
|
+
revision: request.revision,
|
|
20
|
+
version: request.version,
|
|
21
|
+
taskIds: request.taskIds,
|
|
22
|
+
requestKey: request.requestKey,
|
|
23
|
+
}, validate));
|
|
24
|
+
}
|
|
25
|
+
let messageId;
|
|
26
|
+
if (request.action === 'send') {
|
|
27
|
+
const allowed = new Set(attachments.map((a) => a.id));
|
|
28
|
+
if (request.attachments?.some((id) => !allowed.has(id)))
|
|
29
|
+
throw new Error('Only attachments listed by get_child_sessions can be forwarded.');
|
|
30
|
+
messageId =
|
|
31
|
+
'coordinator:' +
|
|
32
|
+
createHash('sha256')
|
|
33
|
+
.update(JSON.stringify(request.requestKey
|
|
34
|
+
? ['follow-up', request.requestKey]
|
|
35
|
+
: [options.runId, options.requestId]))
|
|
36
|
+
.digest('hex');
|
|
37
|
+
await bridge.command({
|
|
38
|
+
id: messageId,
|
|
39
|
+
type: 'delegation_reply',
|
|
40
|
+
coordinatorFollowUp: true,
|
|
41
|
+
followUpToken: request.followUpToken,
|
|
42
|
+
version: request.version,
|
|
43
|
+
attemptId: request.attemptId,
|
|
44
|
+
prompt: request.message,
|
|
45
|
+
attachments: request.attachments ?? [],
|
|
46
|
+
}, validate);
|
|
47
|
+
}
|
|
48
|
+
const view = bridge.view();
|
|
49
|
+
const latest = new Map(view.attempts.map((attempt) => [attempt.taskId, attempt]));
|
|
50
|
+
return {
|
|
51
|
+
version: view.version,
|
|
52
|
+
...(launch ? { launch } : {}),
|
|
53
|
+
children: [...latest.values()].map((a) => ({
|
|
54
|
+
attemptId: a.id,
|
|
55
|
+
followUpToken: bridge.followUpToken?.(a.id),
|
|
56
|
+
sessionId: a.sessionId,
|
|
57
|
+
taskId: a.taskId,
|
|
58
|
+
title: a.task.title,
|
|
59
|
+
status: a.status,
|
|
60
|
+
question: a.question,
|
|
61
|
+
blockedReason: a.blockedReason,
|
|
62
|
+
summary: a.result?.summary.slice(0, 2000),
|
|
63
|
+
pendingReplies: a.pendingReplies,
|
|
64
|
+
// Report this attempt's dispatch gate, not the coordinator's older queue.
|
|
65
|
+
// Running and terminal attempts are described by status, not as paused.
|
|
66
|
+
paused: a.status === 'queued' &&
|
|
67
|
+
(!!a.queuePaused || (view.paused && !a.resumeRequested)),
|
|
68
|
+
})),
|
|
69
|
+
attachments,
|
|
70
|
+
...(messageId ? { messageId } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
/** Sign in without loading anything from Vite's workspace or module graph. */
|
|
3
|
+
export function serveDevSignIn(req, res, url) {
|
|
4
|
+
const navigation = req.method === 'GET' &&
|
|
5
|
+
(['/', '/index.html', '/settings'].includes(url.pathname) ||
|
|
6
|
+
/^\/(?:s|agents)\/(?:main|[a-f0-9-]+)\/?$/i.test(url.pathname));
|
|
7
|
+
if (!navigation) {
|
|
8
|
+
res.writeHead(401, {
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
'Cache-Control': 'no-store',
|
|
11
|
+
'X-Content-Type-Options': 'nosniff',
|
|
12
|
+
});
|
|
13
|
+
res.end(JSON.stringify({ error: 'Open a Presubmit access link to connect this browser.' }));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const nonce = randomBytes(24).toString('base64');
|
|
17
|
+
res.writeHead(200, {
|
|
18
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
19
|
+
'Cache-Control': 'no-store',
|
|
20
|
+
'Referrer-Policy': 'no-referrer',
|
|
21
|
+
'X-Content-Type-Options': 'nosniff',
|
|
22
|
+
'Content-Security-Policy': `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'`,
|
|
23
|
+
});
|
|
24
|
+
// The token and return location are read in the browser, never interpolated into HTML.
|
|
25
|
+
res.end(`<!doctype html>
|
|
26
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
27
|
+
<title>Connect to Presubmit</title>
|
|
28
|
+
<style nonce="${nonce}">:root { color-scheme: light dark; font-family: system-ui, sans-serif; } body { max-width: 36rem; margin: 15vh auto; padding: 1.5rem; line-height: 1.5; }</style>
|
|
29
|
+
</head><body><h1>Connect to Presubmit</h1>
|
|
30
|
+
<p id="status" role="status">Open the access link from your Presubmit terminal or Settings → Devices to connect this browser.</p>
|
|
31
|
+
<noscript>Enable JavaScript to connect.</noscript>
|
|
32
|
+
<script nonce="${nonce}">
|
|
33
|
+
void (async () => {
|
|
34
|
+
const url = new URL(location.href);
|
|
35
|
+
const token = url.searchParams.get('token');
|
|
36
|
+
if (!token) return;
|
|
37
|
+
url.searchParams.delete('token');
|
|
38
|
+
history.replaceState(null, '', url.pathname + url.search + url.hash);
|
|
39
|
+
const status = document.getElementById('status');
|
|
40
|
+
status.textContent = 'Connecting…';
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch('/api/auth', {
|
|
43
|
+
method: 'POST', credentials: 'same-origin', cache: 'no-store',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }),
|
|
45
|
+
});
|
|
46
|
+
const result = await response.json();
|
|
47
|
+
if (!response.ok) throw new Error(result.error || 'This access link could not be used. Open a fresh link.');
|
|
48
|
+
location.reload();
|
|
49
|
+
} catch (error) {
|
|
50
|
+
status.textContent = error instanceof Error ? error.message : 'Could not connect. Open the access link again to retry.';
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
</script></body></html>`);
|
|
54
|
+
}
|