frizz 0.13.12 → 0.13.13
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 +3 -3
- package/dist/frizz.js +433 -173
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -84,15 +84,15 @@ The queue is strict about what earns a card, which is what keeps it a real todo
|
|
|
84
84
|
**Threads are built to run without you.** A worker keeps going until it reaches something only you can settle — a product call, a fork where guessing wrong is expensive to undo, an irreversible action — and then it hands back an answerable *question* rather than a wall of text for you to re-read and interpret.
|
|
85
85
|
|
|
86
86
|
<p align="center">
|
|
87
|
-
<img src="assets/question.png" alt="A question card titled Question: 'Should the settings store use SQLite or a JSON file?' with two options numbered 1 and 2 on keycaps, the first tagged RECOMMENDED, a third row numbered 3 for typing something else, and
|
|
87
|
+
<img src="assets/question.png" alt="A question card titled Question: 'Should the settings store use SQLite or a JSON file?' with two options numbered 1 and 2 on keycaps, the first tagged RECOMMENDED, and a third row numbered 3 for typing something else, above the prompt box and its Send button." width="100%">
|
|
88
88
|
</p>
|
|
89
89
|
|
|
90
|
-
Options are numbered: press an option's number or click it to pick it, then Enter to send. A worker marks its own recommendation when it has one. There is always a row for writing something else instead.
|
|
90
|
+
Options are numbered: press an option's number or click it to pick it, then Enter to send it, along with anything typed in the prompt box below. A worker marks its own recommendation when it has one. There is always a row for writing something else instead.
|
|
91
91
|
|
|
92
92
|
When the answer isn't one thing, the same card takes several: check any combination and add a note.
|
|
93
93
|
|
|
94
94
|
<p align="center">
|
|
95
|
-
<img src="assets/question-multi.png" alt="A question card titled Select multiple: 'Which of these findings should I fix in this pass?' with three numbered checkbox options, the first two ticked, a fourth numbered row for adding a note,
|
|
95
|
+
<img src="assets/question-multi.png" alt="A question card titled Select multiple: 'Which of these findings should I fix in this pass?' with three numbered checkbox options, the first two ticked, and a fourth numbered row for adding a note, above the prompt box, whose Send button is lit." width="100%">
|
|
96
96
|
</p>
|
|
97
97
|
|
|
98
98
|
### GitHub
|
package/dist/frizz.js
CHANGED
|
@@ -182,7 +182,7 @@ var init_local_origin = __esm({
|
|
|
182
182
|
|
|
183
183
|
// packages/server/src/access-codes.ts
|
|
184
184
|
import { randomBytes, timingSafeEqual, createHmac } from "node:crypto";
|
|
185
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
185
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
186
186
|
import { dirname, join as join2 } from "node:path";
|
|
187
187
|
function memorySessionDirectory(seed = []) {
|
|
188
188
|
const records = new Map(seed.map((r) => [r.id, r]));
|
|
@@ -227,7 +227,7 @@ function secretsMatch(a, b) {
|
|
|
227
227
|
return left.length === right.length && timingSafeEqual(left, right);
|
|
228
228
|
}
|
|
229
229
|
function fileSessionDirectory(stateDir, now = Date.now) {
|
|
230
|
-
const path = join2(stateDir,
|
|
230
|
+
const path = join2(stateDir, SESSION_DIRECTORY_FILE);
|
|
231
231
|
let records = [];
|
|
232
232
|
try {
|
|
233
233
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
@@ -283,6 +283,39 @@ function loadOrCreateSessionKey(stateDir) {
|
|
|
283
283
|
}
|
|
284
284
|
return rotateSessionKey(stateDir);
|
|
285
285
|
}
|
|
286
|
+
function adoptSessionState(stateDir, others) {
|
|
287
|
+
if (existsSync(join2(stateDir, SESSION_KEY_FILE))) return void 0;
|
|
288
|
+
const usedAt = (dir) => {
|
|
289
|
+
try {
|
|
290
|
+
const key = statSync(join2(dir, SESSION_KEY_FILE));
|
|
291
|
+
if (!key.isFile() || key.size < 32) return void 0;
|
|
292
|
+
try {
|
|
293
|
+
return Math.max(key.mtimeMs, statSync(join2(dir, SESSION_DIRECTORY_FILE)).mtimeMs);
|
|
294
|
+
} catch {
|
|
295
|
+
return key.mtimeMs;
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
let newest;
|
|
302
|
+
for (const dir of others) {
|
|
303
|
+
if (dir === stateDir) continue;
|
|
304
|
+
const at = usedAt(dir);
|
|
305
|
+
if (at !== void 0 && (!newest || at > newest.at)) newest = { dir, at };
|
|
306
|
+
}
|
|
307
|
+
if (!newest) return void 0;
|
|
308
|
+
mkdirSync(stateDir, { recursive: true });
|
|
309
|
+
for (const name of [SESSION_EPOCH_FILE, SESSION_DIRECTORY_FILE, SESSION_KEY_FILE]) {
|
|
310
|
+
try {
|
|
311
|
+
copyFileSync(join2(newest.dir, name), join2(stateDir, name));
|
|
312
|
+
chmodSync(join2(stateDir, name), 384);
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (name === SESSION_KEY_FILE) throw error;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return newest.dir;
|
|
318
|
+
}
|
|
286
319
|
function rotateSessionKey(stateDir) {
|
|
287
320
|
const key = randomBytes(32);
|
|
288
321
|
writeSessionKey(join2(stateDir, SESSION_KEY_FILE), key);
|
|
@@ -327,7 +360,7 @@ function loadSessionState(stateDir, epoch = SESSION_EPOCH) {
|
|
|
327
360
|
const signedOut = advance.advanced ? directory.revokeAll() : 0;
|
|
328
361
|
return { key, directory, rotateKey: () => rotateSessionKey(stateDir), advance, signedOut };
|
|
329
362
|
}
|
|
330
|
-
var CODE_BYTES, SESSION_BYTES, SESSION_ID_BYTES, DEFAULT_CODE_TTL_MS, DEFAULT_SESSION_TTL_MS, SESSION_EPOCH, AccessStore, SESSION_KEY_FILE, SESSION_EPOCH_FILE;
|
|
363
|
+
var CODE_BYTES, SESSION_BYTES, SESSION_ID_BYTES, DEFAULT_CODE_TTL_MS, DEFAULT_SESSION_TTL_MS, SESSION_EPOCH, AccessStore, SESSION_KEY_FILE, SESSION_EPOCH_FILE, SESSION_DIRECTORY_FILE;
|
|
331
364
|
var init_access_codes = __esm({
|
|
332
365
|
"packages/server/src/access-codes.ts"() {
|
|
333
366
|
"use strict";
|
|
@@ -504,6 +537,7 @@ var init_access_codes = __esm({
|
|
|
504
537
|
};
|
|
505
538
|
SESSION_KEY_FILE = "session-key";
|
|
506
539
|
SESSION_EPOCH_FILE = "session-epoch";
|
|
540
|
+
SESSION_DIRECTORY_FILE = "sessions.json";
|
|
507
541
|
}
|
|
508
542
|
});
|
|
509
543
|
|
|
@@ -518,7 +552,7 @@ var init_local_path = __esm({
|
|
|
518
552
|
});
|
|
519
553
|
|
|
520
554
|
// packages/server/src/local-image.ts
|
|
521
|
-
import { createReadStream, realpathSync, statSync } from "node:fs";
|
|
555
|
+
import { createReadStream, realpathSync, statSync as statSync2 } from "node:fs";
|
|
522
556
|
import { extname, isAbsolute } from "node:path";
|
|
523
557
|
function resolveLocalImage(rawPath, range) {
|
|
524
558
|
if (rawPath) rawPath = normalizeLocalPath(rawPath);
|
|
@@ -533,7 +567,7 @@ function resolveLocalImage(rawPath, range) {
|
|
|
533
567
|
}
|
|
534
568
|
let size;
|
|
535
569
|
try {
|
|
536
|
-
const stat =
|
|
570
|
+
const stat = statSync2(real);
|
|
537
571
|
if (!stat.isFile()) return { status: 404 };
|
|
538
572
|
size = stat.size;
|
|
539
573
|
} catch {
|
|
@@ -944,7 +978,7 @@ import {
|
|
|
944
978
|
openSync,
|
|
945
979
|
readdirSync,
|
|
946
980
|
rmSync,
|
|
947
|
-
statSync as
|
|
981
|
+
statSync as statSync3,
|
|
948
982
|
symlinkSync,
|
|
949
983
|
writeFileSync as writeFileSync2,
|
|
950
984
|
writeSync
|
|
@@ -989,7 +1023,7 @@ function pruneRunLogs(dir, keep = RETAINED_RUNS, days = RETAINED_DAYS, now = Dat
|
|
|
989
1023
|
}
|
|
990
1024
|
const dated = entries.map((name) => {
|
|
991
1025
|
try {
|
|
992
|
-
return { name, at:
|
|
1026
|
+
return { name, at: statSync3(join4(dir, name)).mtimeMs };
|
|
993
1027
|
} catch {
|
|
994
1028
|
return { name, at: 0 };
|
|
995
1029
|
}
|
|
@@ -13468,14 +13502,14 @@ function signoffNudgeText(needsInput) {
|
|
|
13468
13502
|
"the write-up.",
|
|
13469
13503
|
"",
|
|
13470
13504
|
"**DECIDE RATHER THAN ASK.** Stop only for a decision that is genuinely the human's AND that blocks you",
|
|
13471
|
-
"right now: register that one with `
|
|
13505
|
+
"right now: register that one with `frizzctl ask`. Every other open choice INSIDE the task \u2014 a name, a",
|
|
13472
13506
|
"default, a reversible design call \u2014 is yours to make: decide it, say in one line which way you went and",
|
|
13473
13507
|
"what would reverse it, and carry on. A choice that would ENLARGE the task is not one of those: an",
|
|
13474
13508
|
"unanswered question is not permission to go build the answer.",
|
|
13475
13509
|
"",
|
|
13476
13510
|
"Otherwise, sign off \u2014 a registration, or a fence at the END of your next message:",
|
|
13477
13511
|
"",
|
|
13478
|
-
"- `
|
|
13512
|
+
"- `frizzctl ask` \u2014 you need the human. NOT a fence: the ```question fence is retired, and a fence",
|
|
13479
13513
|
" with a question in its body is plain prose. Register it (options with one-line trade-offs, the",
|
|
13480
13514
|
" recommended one first), then rest normally \u2014 an open registered question is the sign-off.",
|
|
13481
13515
|
"- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
|
|
@@ -13485,8 +13519,8 @@ function signoffNudgeText(needsInput) {
|
|
|
13485
13519
|
"",
|
|
13486
13520
|
" Frizz CHECKS every line: name something that is not running and you are bumped rather than parked.",
|
|
13487
13521
|
" A fence that names NOTHING is not a park at all \u2014 if you are not waiting on anything, you are not",
|
|
13488
|
-
" awaiting, you are done. Register a PR with `
|
|
13489
|
-
" `
|
|
13522
|
+
" awaiting, you are done. Register a PR with `frizzctl watch-pr add` and a timer with",
|
|
13523
|
+
" `frizzctl timer set`; `frizzctl activity` reads back everything you have running, with its id.",
|
|
13490
13524
|
"- `` ```awaiting `` with `steps:` \u2014 the human must PERFORM something you cannot: sign in, approve,",
|
|
13491
13525
|
" merge, press a button you may not. List each step as a `- ` line under `steps:`, written to be",
|
|
13492
13526
|
" followed cold: a real Markdown link to every page and file it names, every command in a code span,",
|
|
@@ -13495,14 +13529,14 @@ function signoffNudgeText(needsInput) {
|
|
|
13495
13529
|
" comes back to you as their reply; anything else they need to say comes as a message of their own.",
|
|
13496
13530
|
"- `` ```awaiting `` with `questions:` \u2014 a question you registered at an EARLIER rest is still open and",
|
|
13497
13531
|
" you still need its answer. Name every such question (`questions: [qst_\u2026]`) and withdraw the rest with",
|
|
13498
|
-
" `
|
|
13532
|
+
" `frizzctl unask <id>`: a fence that leaves an open question out is refused. A named card is drawn at",
|
|
13499
13533
|
" this rest; an unnamed one stays where you asked it.",
|
|
13500
13534
|
"",
|
|
13501
13535
|
"**STILL OWED counts things you are not going to do yourself.** A decision you are RECOMMENDING, a",
|
|
13502
13536
|
"draft you wrote but did not send, follow-up work you discovered \u2014 all of it dies with the card, even",
|
|
13503
13537
|
"the part that is someone else's to do. Each ends on a question with your recommendation as option 1,",
|
|
13504
13538
|
"or you DO it first \u2014 a sub-agent's result comes BACK to you, so it lands on your card; a new card via",
|
|
13505
|
-
"`
|
|
13539
|
+
"`frizzctl spawn-thread` is the LAST resort, since nothing it learns returns to you or its siblings.",
|
|
13506
13540
|
"None of them is a `done`. And what is not",
|
|
13507
13541
|
'worth a card is not worth a SENTENCE: delete the dangling "one thing to carry forward", never park it',
|
|
13508
13542
|
"in the handoff.",
|
|
@@ -13525,7 +13559,7 @@ function requireEffortOutsideAcp(profile, ctx) {
|
|
|
13525
13559
|
function fallbackPort(base) {
|
|
13526
13560
|
return base + 1e4;
|
|
13527
13561
|
}
|
|
13528
|
-
var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ClaudeModel, AcpAgent, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, SteerThreadInput, SteerThreadResult, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput,
|
|
13562
|
+
var import_yaml, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ClaudeModel, AcpAgent, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, COMMAND_WATCH_EVERY_MAX_MS, COMMAND_WATCH_FOR_MAX_MS, COMMAND_WATCH_COMMAND_MAX, COMMAND_WATCH_RUN_TIMEOUT_MS, CommandWatchRun, CommandWatchState, CommandWatchView, AddOwnCommandWatchInput, AddOwnCommandWatchResult, DropOwnCommandWatchInput, DropOwnCommandWatchResult, ListOwnCommandWatchesInput, OwnCommandWatchesResult, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, SteerThreadInput, SteerThreadResult, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, SetThreadProfileInput, SetThreadProfileResult, UpgradeThreadModelInput, UpgradeThreadModelResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, HUMAN_GAP_FLOOR_MS, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, ProjectRailCounts, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
|
|
13529
13563
|
var init_src = __esm({
|
|
13530
13564
|
"packages/shared/src/index.ts"() {
|
|
13531
13565
|
"use strict";
|
|
@@ -13863,7 +13897,7 @@ var init_src = __esm({
|
|
|
13863
13897
|
questions: external_exports.array(AskQuestion)
|
|
13864
13898
|
});
|
|
13865
13899
|
AwaitingHint = external_exports.object({
|
|
13866
|
-
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "for", "title", "status", "needs_input", "step", "question"]),
|
|
13900
|
+
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "command", "for", "title", "status", "needs_input", "step", "question"]),
|
|
13867
13901
|
value: external_exports.string()
|
|
13868
13902
|
});
|
|
13869
13903
|
RETIRED_AWAITING_KINDS = ["watch", "pr-watch", "human", "ci", "session", "shell", "agent", "timer", "pr", "reason"];
|
|
@@ -14021,7 +14055,7 @@ var init_src = __esm({
|
|
|
14021
14055
|
* still BOUNDS the poll; the point of the field is that a worker chooses, and one that cannot choose
|
|
14022
14056
|
* is better bounded than broken. */
|
|
14023
14057
|
for: external_exports.string().trim().min(1).max(16).optional(),
|
|
14024
|
-
/** What `target` names. `issue` (from `
|
|
14058
|
+
/** What `target` names. `issue` (from `frizzctl watch-issue`, 2026-09-14) registers a watcher on a
|
|
14025
14059
|
* GitHub issue — same registry, same poll cadence, same drop verb, but a different probe (`gh issue
|
|
14026
14060
|
* view`), a different GraphQL fragment and a report with no CI in it. ABSENT MEANS A PULL REQUEST,
|
|
14027
14061
|
* for the same reason `for` is optional on the wire: every `watch_pr` binary dispatched before this
|
|
@@ -14109,6 +14143,68 @@ var init_src = __esm({
|
|
|
14109
14143
|
dropped: external_exports.boolean(),
|
|
14110
14144
|
watches: external_exports.array(OwnWatchView)
|
|
14111
14145
|
}).strict();
|
|
14146
|
+
COMMAND_WATCH_EVERY_MAX_MS = 24 * 60 * 60 * 1e3;
|
|
14147
|
+
COMMAND_WATCH_FOR_MAX_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14148
|
+
COMMAND_WATCH_COMMAND_MAX = 4e3;
|
|
14149
|
+
COMMAND_WATCH_RUN_TIMEOUT_MS = 2 * 6e4;
|
|
14150
|
+
CommandWatchRun = external_exports.object({
|
|
14151
|
+
at: external_exports.string(),
|
|
14152
|
+
outcome: external_exports.enum(["exit", "timeout", "signal", "error"]),
|
|
14153
|
+
exitCode: external_exports.number().int().nullable(),
|
|
14154
|
+
output: external_exports.string()
|
|
14155
|
+
}).strict();
|
|
14156
|
+
CommandWatchState = external_exports.enum(["armed", "dropped", "passed", "expired", "cancelled"]);
|
|
14157
|
+
CommandWatchView = external_exports.object({
|
|
14158
|
+
id: external_exports.string(),
|
|
14159
|
+
command: external_exports.string(),
|
|
14160
|
+
/** The cadence in the house duration grammar (`5m`, `1h 30m`). */
|
|
14161
|
+
every: external_exports.string(),
|
|
14162
|
+
state: CommandWatchState,
|
|
14163
|
+
createdAt: external_exports.string(),
|
|
14164
|
+
expiresAt: external_exports.string(),
|
|
14165
|
+
/** How many checks have run since it was armed, the registration's own first check included. */
|
|
14166
|
+
runs: external_exports.number().int().nonnegative(),
|
|
14167
|
+
lastRun: CommandWatchRun.optional()
|
|
14168
|
+
}).strict();
|
|
14169
|
+
AddOwnCommandWatchInput = external_exports.object({
|
|
14170
|
+
slug: ThreadSlug,
|
|
14171
|
+
/** Run with `/bin/sh -c` in the project root, with the server's environment. Exit status 0 is "passed". */
|
|
14172
|
+
command: external_exports.string().trim().min(1).max(COMMAND_WATCH_COMMAND_MAX),
|
|
14173
|
+
/** How often to check, as a DURATION (`5m`; parseAwaitingDurationRaw). Clamped to [1m, 24h] and reported. */
|
|
14174
|
+
every: external_exports.string().trim().min(1).max(16),
|
|
14175
|
+
/** How long to keep checking, as a DURATION. Clamped to COMMAND_WATCH_FOR_MAX_MS and reported. */
|
|
14176
|
+
for: external_exports.string().trim().min(1).max(16)
|
|
14177
|
+
}).strict();
|
|
14178
|
+
AddOwnCommandWatchResult = external_exports.object({
|
|
14179
|
+
/** Present when the call was REFUSED — the thread may not run commands outside the CLI's permission
|
|
14180
|
+
* system, or the command cannot run here at all. A refusal is an answer, not a fault: the text says
|
|
14181
|
+
* why and what to use instead, and nothing was stored. */
|
|
14182
|
+
refused: external_exports.string().optional(),
|
|
14183
|
+
/** The armed row's id. Absent when nothing was armed: a refusal, or a first check that already passed. */
|
|
14184
|
+
id: external_exports.string().optional(),
|
|
14185
|
+
/** This exact command was already watched on this thread, so the call registered nothing new. */
|
|
14186
|
+
alreadyArmed: external_exports.boolean().default(false),
|
|
14187
|
+
/** The FIRST CHECK, run before anything was stored. Exit 0 there means the condition already holds:
|
|
14188
|
+
* nothing is armed, and the worker proceeds now rather than waiting a cadence to be told so. */
|
|
14189
|
+
firstRun: CommandWatchRun.optional(),
|
|
14190
|
+
passed: external_exports.boolean().default(false),
|
|
14191
|
+
expiresAt: external_exports.string().optional(),
|
|
14192
|
+
every: external_exports.string().optional(),
|
|
14193
|
+
/** The `every` / `for` the worker wrote, present ONLY when it was clamped — a clamp nobody is told about
|
|
14194
|
+
* is a worker resting on coverage it does not have. */
|
|
14195
|
+
clamped: external_exports.object({ every: external_exports.string().optional(), for: external_exports.string().optional() }).strict().optional(),
|
|
14196
|
+
watches: external_exports.array(CommandWatchView)
|
|
14197
|
+
}).strict();
|
|
14198
|
+
DropOwnCommandWatchInput = external_exports.object({
|
|
14199
|
+
slug: ThreadSlug,
|
|
14200
|
+
id: external_exports.string().min(1).max(64)
|
|
14201
|
+
}).strict();
|
|
14202
|
+
DropOwnCommandWatchResult = external_exports.object({
|
|
14203
|
+
dropped: external_exports.boolean(),
|
|
14204
|
+
watches: external_exports.array(CommandWatchView)
|
|
14205
|
+
}).strict();
|
|
14206
|
+
ListOwnCommandWatchesInput = external_exports.object({ slug: ThreadSlug }).strict();
|
|
14207
|
+
OwnCommandWatchesResult = external_exports.object({ watches: external_exports.array(CommandWatchView) }).strict();
|
|
14112
14208
|
AskQuestionKind = external_exports.enum(["question", "multi"]);
|
|
14113
14209
|
AskedOptionSchema = external_exports.lazy(() => external_exports.object({
|
|
14114
14210
|
// 400, not the 120 it launched with (2026-09-03): a label is a chip line, but "Post the drafted
|
|
@@ -14140,8 +14236,8 @@ var init_src = __esm({
|
|
|
14140
14236
|
}).strict());
|
|
14141
14237
|
AskInput = external_exports.object({
|
|
14142
14238
|
slug: ThreadSlug,
|
|
14143
|
-
// No cap on the count (four until 2026-09-03): the
|
|
14144
|
-
//
|
|
14239
|
+
// No cap on the count (four until 2026-09-03): the command's own help says several at once is one
|
|
14240
|
+
// run — register them together — and a cap here told a worker with six to batch and then refused it.
|
|
14145
14241
|
questions: external_exports.array(AskedQuestionSchema).min(1)
|
|
14146
14242
|
}).strict();
|
|
14147
14243
|
RegisteredQuestionView = external_exports.object({
|
|
@@ -14235,7 +14331,7 @@ var init_src = __esm({
|
|
|
14235
14331
|
body: external_exports.string(),
|
|
14236
14332
|
// fence body minus hint lines, capped server-side; may be ""
|
|
14237
14333
|
hints: external_exports.array(AwaitingHint).default([]),
|
|
14238
|
-
// Present only on a completion the worker REGISTERED (`
|
|
14334
|
+
// Present only on a completion the worker REGISTERED (`frizzctl done`, board.registeredDoneFence)
|
|
14239
14335
|
// rather than wrote as a ```done fence in its final message. The two are one fence to every predicate,
|
|
14240
14336
|
// but the TRANSCRIPT draws a fence card from the message text it parses — so a registered done, which
|
|
14241
14337
|
// is in no message, needs the client to know it must draw the card itself at the bottom of the thread
|
|
@@ -14805,7 +14901,19 @@ var init_src = __esm({
|
|
|
14805
14901
|
// Binds the call to the session the tab is looking at, so a stale page cannot deliver a follow-up
|
|
14806
14902
|
// into a thread that has since been re-dispatched (merged from origin/main, 2026-07-21).
|
|
14807
14903
|
sessionId: external_exports.string().min(1),
|
|
14808
|
-
|
|
14904
|
+
// EMPTY only when `answers` ride: the staged answers alone are a message. The server refuses a send
|
|
14905
|
+
// that carries neither.
|
|
14906
|
+
message: external_exports.string(),
|
|
14907
|
+
// REGISTERED-QUESTION ANSWERS SENT WITH THIS MESSAGE — the prompt box's one Send. The server settles
|
|
14908
|
+
// each question exactly as `answerQuestions` does, then delivers ONE turn: the answers in their wire
|
|
14909
|
+
// form (questionAnswerMessage) and `message` under them as the note (withAnswersNote). They ride the
|
|
14910
|
+
// follow-up rather than the scheduler's answer wake so the pair arrives together, in the order the
|
|
14911
|
+
// human wrote it, and as fast as a steer does.
|
|
14912
|
+
answers: external_exports.array(QuestionAnswerSchema).optional(),
|
|
14913
|
+
// SEND TO THE QUEUE: while the agent is mid-turn, Frizz keeps this message and delivers it when the
|
|
14914
|
+
// thread comes to rest, so it opens the next turn instead of steering this one. At rest it is an
|
|
14915
|
+
// ordinary send — there is nothing to wait for. See `held` in the server's delivery-ledger.ts.
|
|
14916
|
+
hold: external_exports.boolean().optional(),
|
|
14809
14917
|
// Generated once before the optimistic clear so a transport replay can be idempotent.
|
|
14810
14918
|
deliveryId: external_exports.string().min(1).max(200).optional(),
|
|
14811
14919
|
// Retire the worker's live process before delivering, so this message lands in a `claude` that has
|
|
@@ -14867,11 +14975,19 @@ var init_src = __esm({
|
|
|
14867
14975
|
DeliverQueuedNowInput = external_exports.object({
|
|
14868
14976
|
slug: ThreadSlug,
|
|
14869
14977
|
// Same staleness guard as unqueueFollowUp: a stale tab must not preempt a re-dispatched session.
|
|
14870
|
-
sessionId: external_exports.string().min(1)
|
|
14978
|
+
sessionId: external_exports.string().min(1),
|
|
14979
|
+
// Hand over what Frizz is HOLDING for the agent's rest ("Send to queue") and stop there — no
|
|
14980
|
+
// interrupt. The "Send now" under a held bubble: the operator changed their mind about waiting, not
|
|
14981
|
+
// about interrupting, so the message goes in as the steer it would have been.
|
|
14982
|
+
releaseOnly: external_exports.boolean().optional()
|
|
14871
14983
|
}).strict();
|
|
14872
14984
|
DeliverQueuedNowResult = external_exports.object({
|
|
14873
14985
|
interrupted: external_exports.boolean(),
|
|
14874
|
-
reason: external_exports.string().optional()
|
|
14986
|
+
reason: external_exports.string().optional(),
|
|
14987
|
+
// How many messages Frizz was HOLDING for the agent's rest ("Send to queue") and has now handed over.
|
|
14988
|
+
// Those were never in a provider queue, so on a runtime that cannot be interrupted this is the whole
|
|
14989
|
+
// of what "send now" did — and it is a success the surface must not report as a refusal.
|
|
14990
|
+
released: external_exports.number().int().nonnegative().optional()
|
|
14875
14991
|
}).strict();
|
|
14876
14992
|
CompactThreadInput = external_exports.object({
|
|
14877
14993
|
slug: ThreadSlug,
|
|
@@ -14949,7 +15065,7 @@ var init_src = __esm({
|
|
|
14949
15065
|
timers: external_exports.array(ThreadTimerView)
|
|
14950
15066
|
}).strict();
|
|
14951
15067
|
ThreadActivityItem = external_exports.object({
|
|
14952
|
-
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue"]),
|
|
15068
|
+
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "command"]),
|
|
14953
15069
|
/** The string a `<kind>:` fence line must carry. For a shell that is the runtime task id the worker
|
|
14954
15070
|
* was shown; for a PR, `owner/repo#N`; for a timer, its `tmr_…` row id. */
|
|
14955
15071
|
id: external_exports.string(),
|
|
@@ -14998,13 +15114,6 @@ var init_src = __esm({
|
|
|
14998
15114
|
intervalSeconds: RecurringIntervalSeconds.optional(),
|
|
14999
15115
|
enabled: external_exports.boolean().optional()
|
|
15000
15116
|
}).strict();
|
|
15001
|
-
ThreadPluginReloadResult = external_exports.object({
|
|
15002
|
-
plugins: external_exports.number().int().min(0),
|
|
15003
|
-
commands: external_exports.number().int().min(0),
|
|
15004
|
-
agents: external_exports.number().int().min(0),
|
|
15005
|
-
mcpServers: external_exports.array(external_exports.string()),
|
|
15006
|
-
errorCount: external_exports.number().int().min(0)
|
|
15007
|
-
}).strict();
|
|
15008
15117
|
RenameThreadInput = external_exports.object({
|
|
15009
15118
|
slug: ThreadSlug,
|
|
15010
15119
|
title: external_exports.string().trim().min(1).max(200)
|
|
@@ -15247,6 +15356,14 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15247
15356
|
interactionId: InteractionOpaqueId,
|
|
15248
15357
|
lifecycle: InteractionLifecycle,
|
|
15249
15358
|
recordRevision: InteractionRevision
|
|
15359
|
+
}).strict(),
|
|
15360
|
+
external_exports.object({
|
|
15361
|
+
// The machine's PROJECT LIST changed: a reorder, a rename, an icon, a project added or forgotten.
|
|
15362
|
+
// Payload-free for the same reason as above — the list is one cheap read, and a client that
|
|
15363
|
+
// re-reads it cannot apply a stale copy out of order. It is the one event that is not about this
|
|
15364
|
+
// stream's own project: the server publishes it on EVERY open project's stream, because the rail
|
|
15365
|
+
// and the grid draw the same list in every tab whichever board that tab is on.
|
|
15366
|
+
type: external_exports.literal("projects-changed")
|
|
15250
15367
|
}).strict()
|
|
15251
15368
|
]);
|
|
15252
15369
|
TranscriptEdit = external_exports.object({
|
|
@@ -15490,7 +15607,8 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15490
15607
|
// the server's from the instant it arrives. "failed": the transport threw, or never answered — the
|
|
15491
15608
|
// text is kept until the operator retries, edits or dismisses it, and nothing retries it on its own.
|
|
15492
15609
|
// Additive + optional: an older client renders both as a plain bubble (gray for "sending").
|
|
15493
|
-
|
|
15610
|
+
// "held": the operator sent it TO THE QUEUE — Frizz has it and no transport does, until the agent rests.
|
|
15611
|
+
deliveryState: external_exports.enum(["sending", "pending", "enqueued", "delivered", "unconfirmed", "failed", "held"]).optional(),
|
|
15494
15612
|
// Why a "failed" send failed, verbatim from the transport. Set only alongside deliveryState "failed".
|
|
15495
15613
|
deliveryError: external_exports.string().optional(),
|
|
15496
15614
|
// FRIZZ wrote this user turn, not the human: it is a scheduler wake delivery (isWakeDelivery). The
|
|
@@ -15642,8 +15760,9 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15642
15760
|
* Three states, not a boolean, because the two "no icon to draw right now" cases must behave
|
|
15643
15761
|
* differently and a boolean collapses them:
|
|
15644
15762
|
* · `icon` — one is stored; draw it.
|
|
15645
|
-
* · `none` — scanned, nothing found. Draw the monogram and DO NOT request the icon
|
|
15646
|
-
* which is what stops an iconless project flashing its initials and then
|
|
15763
|
+
* · `none` — scanned recently, nothing found. Draw the monogram and DO NOT request the icon
|
|
15764
|
+
* route, which is what stops an iconless project flashing its initials and then
|
|
15765
|
+
* swapping. An OLD miss reads `unknown` again, so an icon added since is found.
|
|
15647
15766
|
* · `unknown` — never scanned. The monogram shows, but the request MUST still go out, because
|
|
15648
15767
|
* that request is what triggers the (lazy, cached) scan in the first place.
|
|
15649
15768
|
*
|
|
@@ -20120,7 +20239,7 @@ import {
|
|
|
20120
20239
|
realpathSync as realpathSync2,
|
|
20121
20240
|
renameSync,
|
|
20122
20241
|
rmSync as rmSync3,
|
|
20123
|
-
statSync as
|
|
20242
|
+
statSync as statSync4,
|
|
20124
20243
|
writeFileSync as writeFileSync5
|
|
20125
20244
|
} from "node:fs";
|
|
20126
20245
|
import { homedir as homedir7 } from "node:os";
|
|
@@ -20171,7 +20290,7 @@ function syncDirectory(path) {
|
|
|
20171
20290
|
}
|
|
20172
20291
|
function lockOwnerPath(lockPath) {
|
|
20173
20292
|
try {
|
|
20174
|
-
return
|
|
20293
|
+
return statSync4(lockPath).isDirectory() ? join8(lockPath, GLOBAL_LOCK_OWNER) : lockPath;
|
|
20175
20294
|
} catch {
|
|
20176
20295
|
return lockPath;
|
|
20177
20296
|
}
|
|
@@ -20202,10 +20321,10 @@ function observeLock(lockPath, adapter) {
|
|
|
20202
20321
|
}
|
|
20203
20322
|
try {
|
|
20204
20323
|
const ownerPath = lockOwnerPath(lockPath);
|
|
20205
|
-
let modified =
|
|
20324
|
+
let modified = statSync4(lockPath).mtimeMs;
|
|
20206
20325
|
if (ownerPath !== lockPath) {
|
|
20207
20326
|
try {
|
|
20208
|
-
modified = Math.max(modified,
|
|
20327
|
+
modified = Math.max(modified, statSync4(ownerPath).mtimeMs);
|
|
20209
20328
|
} catch {
|
|
20210
20329
|
}
|
|
20211
20330
|
}
|
|
@@ -20536,7 +20655,7 @@ import {
|
|
|
20536
20655
|
readFileSync as readFileSync6,
|
|
20537
20656
|
renameSync as renameSync2,
|
|
20538
20657
|
rmSync as rmSync4,
|
|
20539
|
-
statSync as
|
|
20658
|
+
statSync as statSync5,
|
|
20540
20659
|
writeFileSync as writeFileSync6
|
|
20541
20660
|
} from "node:fs";
|
|
20542
20661
|
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join9 } from "node:path";
|
|
@@ -20603,7 +20722,7 @@ function quarantine(path, suffix) {
|
|
|
20603
20722
|
}
|
|
20604
20723
|
function pathAge(path) {
|
|
20605
20724
|
try {
|
|
20606
|
-
return Date.now() -
|
|
20725
|
+
return Date.now() - statSync5(path).mtimeMs;
|
|
20607
20726
|
} catch {
|
|
20608
20727
|
return void 0;
|
|
20609
20728
|
}
|
|
@@ -21009,7 +21128,7 @@ var init_boot_progress = __esm({
|
|
|
21009
21128
|
|
|
21010
21129
|
// packages/server/src/project-root.ts
|
|
21011
21130
|
import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
|
|
21012
|
-
import { closeSync as closeSync3, existsSync as existsSync4, fsyncSync as fsyncSync3, mkdirSync as mkdirSync8, openSync as openSync4, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync4, rmSync as rmSync6,
|
|
21131
|
+
import { closeSync as closeSync3, existsSync as existsSync4, fsyncSync as fsyncSync3, mkdirSync as mkdirSync8, openSync as openSync4, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
21013
21132
|
import { homedir as homedir8 } from "node:os";
|
|
21014
21133
|
import { dirname as dirname8, join as join11, parse, resolve as resolve3 } from "node:path";
|
|
21015
21134
|
function projectIdPath(root) {
|
|
@@ -21103,17 +21222,6 @@ function isHomeDirectory(dir, home = homedir8()) {
|
|
|
21103
21222
|
return false;
|
|
21104
21223
|
}
|
|
21105
21224
|
}
|
|
21106
|
-
function isWorldWritable(dir, platform = process.platform) {
|
|
21107
|
-
if (platform === "win32") return false;
|
|
21108
|
-
try {
|
|
21109
|
-
return (statSync5(dir).mode & 2) !== 0;
|
|
21110
|
-
} catch {
|
|
21111
|
-
return false;
|
|
21112
|
-
}
|
|
21113
|
-
}
|
|
21114
|
-
function refuseWorldWritableProject(dir) {
|
|
21115
|
-
if (isWorldWritable(dir)) throw new Error(`A folder every account can write to cannot be a project: ${dir}`);
|
|
21116
|
-
}
|
|
21117
21225
|
function existingProjectId(dir) {
|
|
21118
21226
|
const fromFile = readProjectIdFile(dir);
|
|
21119
21227
|
if (fromFile) return fromFile;
|
|
@@ -21190,10 +21298,11 @@ function readRegistry(home = homedir9()) {
|
|
|
21190
21298
|
const parsed = JSON.parse(raw);
|
|
21191
21299
|
if (parsed?.version !== 1 || !Array.isArray(parsed.projects)) return { ...EMPTY, projects: [] };
|
|
21192
21300
|
const forgotten = Array.isArray(parsed.forgotten) ? parsed.forgotten.filter((id) => typeof id === "string") : [];
|
|
21301
|
+
const host = canonicalPath(launchHostDir(home));
|
|
21193
21302
|
return {
|
|
21194
21303
|
version: 1,
|
|
21195
21304
|
projects: parsed.projects.filter(
|
|
21196
|
-
(p) => p && typeof p.id === "string" && typeof p.path === "string" && typeof p.slug === "string"
|
|
21305
|
+
(p) => p && typeof p.id === "string" && typeof p.path === "string" && typeof p.slug === "string" && p.path !== host
|
|
21197
21306
|
),
|
|
21198
21307
|
...forgotten.length > 0 ? { forgotten } : {}
|
|
21199
21308
|
};
|
|
@@ -21227,6 +21336,12 @@ function writeRegistry(registry, home = homedir9()) {
|
|
|
21227
21336
|
}
|
|
21228
21337
|
throw error;
|
|
21229
21338
|
}
|
|
21339
|
+
for (const listener of writeListeners) {
|
|
21340
|
+
try {
|
|
21341
|
+
listener(path);
|
|
21342
|
+
} catch {
|
|
21343
|
+
}
|
|
21344
|
+
}
|
|
21230
21345
|
}
|
|
21231
21346
|
function slugifyProject(value) {
|
|
21232
21347
|
const s = slugify(value);
|
|
@@ -21297,20 +21412,11 @@ function registerProject(input, home = homedir9()) {
|
|
|
21297
21412
|
writeRegistry(registry, home);
|
|
21298
21413
|
return { entry, action: "created" };
|
|
21299
21414
|
}
|
|
21300
|
-
function listProjects(home = homedir9()) {
|
|
21301
|
-
const byRecency = (a, b) => a.lastOpenedAt < b.lastOpenedAt ? 1 : a.lastOpenedAt > b.lastOpenedAt ? -1 : 0;
|
|
21302
|
-
return readRegistry(home).projects.map((p) => ({ ...p, stale: !existsSync5(p.path) })).sort((a, b) => {
|
|
21303
|
-
if (a.order === void 0 && b.order === void 0) return byRecency(a, b);
|
|
21304
|
-
if (a.order === void 0) return 1;
|
|
21305
|
-
if (b.order === void 0) return -1;
|
|
21306
|
-
return a.order - b.order;
|
|
21307
|
-
});
|
|
21308
|
-
}
|
|
21309
21415
|
function findByPath(path, home = homedir9()) {
|
|
21310
21416
|
const canonical = canonicalPath(path);
|
|
21311
21417
|
return readRegistry(home).projects.find((p) => p.path === canonical || p.path === path);
|
|
21312
21418
|
}
|
|
21313
|
-
var EMPTY, GENERIC, RESERVED;
|
|
21419
|
+
var EMPTY, GENERIC, RESERVED, writeListeners, ICON_RESCAN_AFTER_MS;
|
|
21314
21420
|
var init_project_registry = __esm({
|
|
21315
21421
|
"packages/server/src/project-registry.ts"() {
|
|
21316
21422
|
"use strict";
|
|
@@ -21356,15 +21462,55 @@ var init_project_registry = __esm({
|
|
|
21356
21462
|
"api",
|
|
21357
21463
|
"health"
|
|
21358
21464
|
]);
|
|
21465
|
+
writeListeners = /* @__PURE__ */ new Set();
|
|
21466
|
+
ICON_RESCAN_AFTER_MS = 10 * 60 * 1e3;
|
|
21467
|
+
}
|
|
21468
|
+
});
|
|
21469
|
+
|
|
21470
|
+
// packages/server/src/launch-routing.ts
|
|
21471
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
21472
|
+
import { homedir as homedir10 } from "node:os";
|
|
21473
|
+
function classifyLaunch(dir, home = homedir10()) {
|
|
21474
|
+
const candidate = realpathSync5(discoverProjectRoot(dir, home));
|
|
21475
|
+
const refused = isHomeDirectory(candidate, home);
|
|
21476
|
+
if (!refused && (isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0)) {
|
|
21477
|
+
return { kind: "open", root: candidate };
|
|
21478
|
+
}
|
|
21479
|
+
return !refused && hasProjectMarker(candidate) ? { kind: "offer", directory: candidate } : { kind: "grid" };
|
|
21480
|
+
}
|
|
21481
|
+
function offerPath(directory) {
|
|
21482
|
+
return `/?add=${encodeURIComponent(directory)}`;
|
|
21483
|
+
}
|
|
21484
|
+
function parseLaunchRoute(body) {
|
|
21485
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
21486
|
+
const { protocol, path, project, notice } = body;
|
|
21487
|
+
if (protocol !== LAUNCH_ROUTE_PROTOCOL || typeof path !== "string") return void 0;
|
|
21488
|
+
if (!path.startsWith("/") || path.startsWith("//") || path.includes("\\") || /[\u0000-\u001f]/u.test(path)) return void 0;
|
|
21489
|
+
const named = project && typeof project === "object" ? project : void 0;
|
|
21490
|
+
return {
|
|
21491
|
+
path,
|
|
21492
|
+
...named && typeof named.name === "string" && typeof named.dir === "string" ? { project: { name: named.name, dir: named.dir } } : {},
|
|
21493
|
+
...typeof notice === "string" && notice.length > 0 ? { notice: notice.slice(0, 500) } : {}
|
|
21494
|
+
};
|
|
21495
|
+
}
|
|
21496
|
+
var LAUNCH_ROUTE_PROTOCOL, LAUNCH_ROUTE_PATH, LAUNCH_TOKEN_HEADER;
|
|
21497
|
+
var init_launch_routing = __esm({
|
|
21498
|
+
"packages/server/src/launch-routing.ts"() {
|
|
21499
|
+
"use strict";
|
|
21500
|
+
init_project_registry();
|
|
21501
|
+
init_project_root();
|
|
21502
|
+
LAUNCH_ROUTE_PROTOCOL = 1;
|
|
21503
|
+
LAUNCH_ROUTE_PATH = "/control/launch";
|
|
21504
|
+
LAUNCH_TOKEN_HEADER = "x-frizz-launch-token";
|
|
21359
21505
|
}
|
|
21360
21506
|
});
|
|
21361
21507
|
|
|
21362
21508
|
// src/production.ts
|
|
21363
21509
|
init_local_origin();
|
|
21364
|
-
import { readFileSync as readFileSync14 } from "node:fs";
|
|
21365
|
-
import { homedir as
|
|
21510
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync14 } from "node:fs";
|
|
21511
|
+
import { homedir as homedir12 } from "node:os";
|
|
21366
21512
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
21367
|
-
import { join as join17 } from "node:path";
|
|
21513
|
+
import { dirname as dirname14, join as join17 } from "node:path";
|
|
21368
21514
|
|
|
21369
21515
|
// src/browser.ts
|
|
21370
21516
|
import { spawn, execFile } from "node:child_process";
|
|
@@ -25459,14 +25605,14 @@ init_frizz_paths();
|
|
|
25459
25605
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25460
25606
|
import { createServer as createServer2 } from "node:net";
|
|
25461
25607
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
25462
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync10, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync10, realpathSync as
|
|
25463
|
-
import { homedir as
|
|
25608
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync10, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync10, realpathSync as realpathSync6, rmSync as rmSync8, symlinkSync as symlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
|
|
25609
|
+
import { homedir as homedir11, tmpdir as tmpdir2 } from "node:os";
|
|
25464
25610
|
import { basename as basename5, dirname as dirname10, join as join13, resolve as resolve4 } from "node:path";
|
|
25465
25611
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
25466
25612
|
init_project_root();
|
|
25613
|
+
init_launch_routing();
|
|
25467
25614
|
init_logging();
|
|
25468
25615
|
init_src();
|
|
25469
|
-
init_project_registry();
|
|
25470
25616
|
var PORT_SCAN_COUNT = 100;
|
|
25471
25617
|
var LAUNCH_TIMEOUT_MS = BOOT_STALL_TIMEOUT_MS;
|
|
25472
25618
|
var LAUNCH_HARD_TIMEOUT_MS = BOOT_HARD_TIMEOUT_MS;
|
|
@@ -25583,7 +25729,7 @@ function boardAddress(url) {
|
|
|
25583
25729
|
return url.includes("?") || url.endsWith("/") ? url : `${url}/`;
|
|
25584
25730
|
}
|
|
25585
25731
|
var SANDBOX_DROPPED_XDG_ROOTS = ["XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"];
|
|
25586
|
-
function prepareSandbox(env = process.env, realHome =
|
|
25732
|
+
function prepareSandbox(env = process.env, realHome = homedir11()) {
|
|
25587
25733
|
const home = mkdtempSync(join13(tmpdir2(), "frizz-sandbox-"));
|
|
25588
25734
|
const inherited = { ...env };
|
|
25589
25735
|
env.HOME = home;
|
|
@@ -25639,34 +25785,46 @@ function cleanupSandbox(home) {
|
|
|
25639
25785
|
} catch {
|
|
25640
25786
|
}
|
|
25641
25787
|
}
|
|
25642
|
-
function resolveLaunchIntent(cwd = process.cwd(), home =
|
|
25643
|
-
const
|
|
25644
|
-
|
|
25645
|
-
const
|
|
25646
|
-
|
|
25647
|
-
const host = mostRecentProject(home, env) ?? hostWorkspace(home);
|
|
25648
|
-
return !refused && hasProjectMarker(candidate) ? { kind: "offer", workspace: host, directory: candidate } : { kind: "grid", workspace: host };
|
|
25788
|
+
function resolveLaunchIntent(cwd = process.cwd(), home = homedir11(), env = process.env) {
|
|
25789
|
+
const launch = classifyLaunch(cwd, home);
|
|
25790
|
+
if (launch.kind === "open") return { kind: "open", workspace: resolveWorkspace(cwd, home, env) };
|
|
25791
|
+
const host = hostWorkspace(home);
|
|
25792
|
+
return launch.kind === "offer" ? { kind: "offer", workspace: host, directory: launch.directory } : { kind: "grid", workspace: host };
|
|
25649
25793
|
}
|
|
25650
|
-
function
|
|
25794
|
+
async function askLaunchRoute(port, ownerToken, dir, options2 = {}) {
|
|
25795
|
+
const fetcher = options2.fetcher ?? fetch;
|
|
25796
|
+
const deadline = Date.now() + (options2.patienceMs ?? 6e3);
|
|
25797
|
+
for (; ; ) {
|
|
25798
|
+
const controller = new AbortController();
|
|
25799
|
+
const timeout = setTimeout(() => controller.abort(), options2.timeoutMs ?? 5e3);
|
|
25800
|
+
timeout.unref?.();
|
|
25801
|
+
try {
|
|
25802
|
+
const response = await fetcher(`http://127.0.0.1:${port}${FRIZZ_ROUTE_PREFIX}${LAUNCH_ROUTE_PATH}`, {
|
|
25803
|
+
method: "POST",
|
|
25804
|
+
headers: { "content-type": "application/json", [LAUNCH_TOKEN_HEADER]: ownerToken },
|
|
25805
|
+
body: JSON.stringify({ protocol: LAUNCH_ROUTE_PROTOCOL, dir, ...options2.args?.length ? { args: options2.args } : {} }),
|
|
25806
|
+
signal: controller.signal
|
|
25807
|
+
});
|
|
25808
|
+
if (response.status === 200) return parseLaunchRoute(await response.json());
|
|
25809
|
+
if (response.status !== 503 || Date.now() >= deadline) return void 0;
|
|
25810
|
+
} catch {
|
|
25811
|
+
return void 0;
|
|
25812
|
+
} finally {
|
|
25813
|
+
clearTimeout(timeout);
|
|
25814
|
+
}
|
|
25815
|
+
await delay2(250);
|
|
25816
|
+
}
|
|
25817
|
+
}
|
|
25818
|
+
function hostWorkspace(home = homedir11()) {
|
|
25651
25819
|
const dir = launchHostDir(home);
|
|
25652
25820
|
mkdirSync10(dir, { recursive: true });
|
|
25653
|
-
const root =
|
|
25821
|
+
const root = realpathSync6(dir);
|
|
25654
25822
|
const id = ensureProjectIdFile(root, home);
|
|
25655
25823
|
const stateDir = projectStateDir(id, home);
|
|
25656
25824
|
mkdirSync10(stateDir, { recursive: true });
|
|
25657
25825
|
return { root, id, stateDir, name: basename5(root), identityScope: "repository" };
|
|
25658
25826
|
}
|
|
25659
|
-
function
|
|
25660
|
-
for (const entry of listProjects(home)) {
|
|
25661
|
-
if (entry.stale || isHomeDirectory(entry.path, home) || isWorldWritable(entry.path)) continue;
|
|
25662
|
-
try {
|
|
25663
|
-
return resolveWorkspace(entry.path, home, env);
|
|
25664
|
-
} catch {
|
|
25665
|
-
}
|
|
25666
|
-
}
|
|
25667
|
-
return void 0;
|
|
25668
|
-
}
|
|
25669
|
-
function resolveWorkspace(cwd = process.cwd(), home = homedir10(), env = process.env) {
|
|
25827
|
+
function resolveWorkspace(cwd = process.cwd(), home = homedir11(), env = process.env) {
|
|
25670
25828
|
let gitRoot;
|
|
25671
25829
|
try {
|
|
25672
25830
|
gitRoot = execFileSync3("git", ["rev-parse", "--show-toplevel"], {
|
|
@@ -25680,8 +25838,7 @@ function resolveWorkspace(cwd = process.cwd(), home = homedir10(), env = process
|
|
|
25680
25838
|
if (!isNotAGitWorktree(error)) throw new Error("unable to resolve Git repository root");
|
|
25681
25839
|
gitRoot = void 0;
|
|
25682
25840
|
}
|
|
25683
|
-
const root0 =
|
|
25684
|
-
refuseWorldWritableProject(root0);
|
|
25841
|
+
const root0 = realpathSync6(gitRoot ?? discoverProjectRoot(cwd, home));
|
|
25685
25842
|
const identity = gitRoot ? resolveGitProjectIdentity(root0, home) : void 0;
|
|
25686
25843
|
const root = identity?.root ?? root0;
|
|
25687
25844
|
const id = ensureProjectIdFile(root, home, identity?.id);
|
|
@@ -25712,7 +25869,7 @@ function workspaceLaunchTarget(workspace2) {
|
|
|
25712
25869
|
function workspaceFromLaunchTarget(target2, env = process.env) {
|
|
25713
25870
|
let root;
|
|
25714
25871
|
try {
|
|
25715
|
-
root =
|
|
25872
|
+
root = realpathSync6(target2.projectDir);
|
|
25716
25873
|
} catch {
|
|
25717
25874
|
throw new Error("pinned Frizz workspace is no longer available");
|
|
25718
25875
|
}
|
|
@@ -26483,9 +26640,25 @@ var Supervisor = class {
|
|
|
26483
26640
|
if (this.restartCompletion === completion) this.restartCompletion = null;
|
|
26484
26641
|
}
|
|
26485
26642
|
}
|
|
26643
|
+
/**
|
|
26644
|
+
* A restart or an update drains the running child and forks its replacement in the project the
|
|
26645
|
+
* server is hosted on. Once that folder is deleted the fork fails with ENOENT, and a child given
|
|
26646
|
+
* any other cwd refuses to boot without its project — and so does the one that would put the old
|
|
26647
|
+
* child back, so the click meant to refresh the board stops it. The running child is unharmed by
|
|
26648
|
+
* the missing folder, so refuse before draining and leave it serving.
|
|
26649
|
+
*
|
|
26650
|
+
* Measured 2026-10-11 on a board hosted on a folder deleted a day later: its Update failed one
|
|
26651
|
+
* step earlier, on npm dying with `uv_cwd ENOENT`, and that accident was all that kept it up.
|
|
26652
|
+
*/
|
|
26653
|
+
missingLaunchFolder() {
|
|
26654
|
+
if (existsSync7(this.cwd)) return void 0;
|
|
26655
|
+
return `Frizz is running on ${this.cwd}, which no longer exists, so a new server cannot start there. Stop Frizz and start it again.`;
|
|
26656
|
+
}
|
|
26486
26657
|
/** Called only by the public supervisor control endpoint, never by ordinary document reloads. */
|
|
26487
26658
|
restartFromBrowser() {
|
|
26488
26659
|
if (this.browserRestart) return this.browserRestart;
|
|
26660
|
+
const missing = this.missingLaunchFolder();
|
|
26661
|
+
if (missing) return Promise.resolve({ state: "failed", message: missing });
|
|
26489
26662
|
const previousBoot = this.boot?.bootId;
|
|
26490
26663
|
const work = (async () => {
|
|
26491
26664
|
this.lastRestartFailure = void 0;
|
|
@@ -26511,6 +26684,8 @@ var Supervisor = class {
|
|
|
26511
26684
|
}
|
|
26512
26685
|
async updateFromBrowser() {
|
|
26513
26686
|
if (!this.updateRestart) return { state: "failed", message: "Update & Restart is unavailable in this launcher mode" };
|
|
26687
|
+
const missing = this.missingLaunchFolder();
|
|
26688
|
+
if (missing) return { state: "failed", message: missing };
|
|
26514
26689
|
if (this.updateMode === "child") return this.updateChildFromBrowser();
|
|
26515
26690
|
this.emitActivity("updating", "preparing the update \u2014 the running Frizz is untouched until it is ready");
|
|
26516
26691
|
const candidate = await this.updateRestart();
|
|
@@ -27220,12 +27395,13 @@ import {
|
|
|
27220
27395
|
mkdirSync as mkdirSync12,
|
|
27221
27396
|
openSync as openSync6,
|
|
27222
27397
|
readFileSync as readFileSync12,
|
|
27223
|
-
realpathSync as
|
|
27398
|
+
realpathSync as realpathSync7,
|
|
27224
27399
|
renameSync as renameSync6,
|
|
27225
27400
|
rmSync as rmSync9,
|
|
27226
27401
|
statSync as statSync6,
|
|
27227
27402
|
writeFileSync as writeFileSync11
|
|
27228
27403
|
} from "node:fs";
|
|
27404
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
27229
27405
|
import { delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute6, join as join15, relative as relative3, resolve as resolve6, sep as sep4 } from "node:path";
|
|
27230
27406
|
var SERVER_PROTOCOL = 1;
|
|
27231
27407
|
var SERVER_DATA_EPOCH = 1;
|
|
@@ -27262,9 +27438,18 @@ function serverReleaseSpec(manifest, packageOverride) {
|
|
|
27262
27438
|
assertSpec(spec);
|
|
27263
27439
|
return spec;
|
|
27264
27440
|
}
|
|
27441
|
+
function serverReleaseMinimum(manifest) {
|
|
27442
|
+
const metadata = record(record(manifest).frizzServer);
|
|
27443
|
+
const minimum = metadata.minimum;
|
|
27444
|
+
if (minimum === void 0) return void 0;
|
|
27445
|
+
if (typeof minimum !== "string" || !VERSION.test(minimum)) throw new Error("invalid minimum Frizz server version");
|
|
27446
|
+
const order = typeof metadata.version === "string" ? compareReleaseVersions(metadata.version, minimum) : null;
|
|
27447
|
+
if (order === null || order < 0) throw new Error("the pinned Frizz server is older than the minimum this launcher declares");
|
|
27448
|
+
return minimum;
|
|
27449
|
+
}
|
|
27265
27450
|
function containedFile(root, name, directory = false) {
|
|
27266
|
-
const path =
|
|
27267
|
-
const rel = relative3(
|
|
27451
|
+
const path = realpathSync7(join15(root, name));
|
|
27452
|
+
const rel = relative3(realpathSync7(root), path);
|
|
27268
27453
|
if (rel === ".." || rel.startsWith(`..${sep4}`) || isAbsolute6(rel))
|
|
27269
27454
|
throw new Error(`Frizz server artifact escapes its package: ${name}`);
|
|
27270
27455
|
const stat = statSync6(path);
|
|
@@ -27286,7 +27471,7 @@ function validateServerGeneration(root, spec, id, compatibility = CURRENT_COMPAT
|
|
|
27286
27471
|
containedFile(root, "web-dist/index.html");
|
|
27287
27472
|
containedFile(root, "runtime/board/index.mjs");
|
|
27288
27473
|
containedFile(root, "runtime/cc-worker/.claude-plugin/plugin.json");
|
|
27289
|
-
return { ...spec, id, root:
|
|
27474
|
+
return { ...spec, id, root: realpathSync7(root), entry };
|
|
27290
27475
|
}
|
|
27291
27476
|
function serverGenerationLaunch(generation) {
|
|
27292
27477
|
return {
|
|
@@ -27305,7 +27490,7 @@ function resolveNpmCli(env = process.env, executable = process.execPath) {
|
|
|
27305
27490
|
for (const directory of [dirname13(executable), ...(env.PATH ?? env.Path ?? "").split(delimiter2).filter(Boolean)]) {
|
|
27306
27491
|
candidates.push(join15(directory, "node_modules/npm/bin/npm-cli.js"), join15(directory, "../lib/node_modules/npm/bin/npm-cli.js"));
|
|
27307
27492
|
try {
|
|
27308
|
-
const path =
|
|
27493
|
+
const path = realpathSync7(join15(directory, "npm"));
|
|
27309
27494
|
if (path.endsWith("npm-cli.js")) candidates.push(path);
|
|
27310
27495
|
} catch {
|
|
27311
27496
|
}
|
|
@@ -27314,7 +27499,15 @@ function resolveNpmCli(env = process.env, executable = process.execPath) {
|
|
|
27314
27499
|
if (!found) throw new Error("npm was not found beside Node or on PATH; install Node with npm to install Frizz server releases");
|
|
27315
27500
|
return resolve6(found);
|
|
27316
27501
|
}
|
|
27317
|
-
|
|
27502
|
+
var NPM_OPERATION_TIMEOUT_MS = 18e4;
|
|
27503
|
+
function npmFailureSummary(stderr) {
|
|
27504
|
+
const lines = stderr.trim().split("\n");
|
|
27505
|
+
const tail = lines.slice(-8);
|
|
27506
|
+
const cause = lines.find((line) => /^(?:[A-Z]\w*)?Error(?: \[\w+\])?: /u.test(line));
|
|
27507
|
+
return (cause && !tail.includes(cause) ? [cause, ...tail] : tail).join("\n");
|
|
27508
|
+
}
|
|
27509
|
+
function npmServerPackageInstaller(env = process.env, options2 = {}) {
|
|
27510
|
+
const timeoutMs = options2.timeoutMs ?? NPM_OPERATION_TIMEOUT_MS;
|
|
27318
27511
|
const run2 = (args2, cwd) => new Promise((resolveOutput, reject) => {
|
|
27319
27512
|
let cli;
|
|
27320
27513
|
try {
|
|
@@ -27328,14 +27521,17 @@ function npmServerPackageInstaller(env = process.env) {
|
|
|
27328
27521
|
cwd,
|
|
27329
27522
|
encoding: "utf8",
|
|
27330
27523
|
windowsHide: true,
|
|
27331
|
-
timeout:
|
|
27524
|
+
timeout: timeoutMs,
|
|
27332
27525
|
killSignal: "SIGKILL",
|
|
27333
27526
|
maxBuffer: 4 * 1024 * 1024
|
|
27334
27527
|
}, (error, stdout, stderr) => {
|
|
27335
|
-
if (error) {
|
|
27336
|
-
|
|
27337
|
-
|
|
27338
|
-
}
|
|
27528
|
+
if (!error) {
|
|
27529
|
+
resolveOutput(stdout);
|
|
27530
|
+
return;
|
|
27531
|
+
}
|
|
27532
|
+
const limit = timeoutMs >= 6e4 ? `${Math.ceil(timeoutMs / 6e4)}m` : `${Math.ceil(timeoutMs / 1e3)}s`;
|
|
27533
|
+
const why = error.killed ? `npm ${args2[0]} did not finish within ${limit} and was stopped` : npmFailureSummary(stderr) || error.message;
|
|
27534
|
+
reject(new Error(`Frizz server npm operation failed: ${why}`));
|
|
27339
27535
|
});
|
|
27340
27536
|
});
|
|
27341
27537
|
return {
|
|
@@ -27359,7 +27555,7 @@ function npmServerPackageInstaller(env = process.env) {
|
|
|
27359
27555
|
},
|
|
27360
27556
|
async latestVersion(packageName) {
|
|
27361
27557
|
if (!PACKAGE.test(packageName)) throw new Error("invalid Frizz server package name");
|
|
27362
|
-
const parsed = JSON.parse(await run2(["view", `${packageName}@latest`, "version", "--json"]));
|
|
27558
|
+
const parsed = JSON.parse(await run2(["view", `${packageName}@latest`, "version", "--json"], tmpdir3()));
|
|
27363
27559
|
const version = Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
27364
27560
|
if (typeof version !== "string" || !VERSION.test(version)) throw new Error("npm returned an invalid Frizz server version");
|
|
27365
27561
|
return version;
|
|
@@ -27393,11 +27589,12 @@ function atomicJson2(path, value) {
|
|
|
27393
27589
|
}
|
|
27394
27590
|
}
|
|
27395
27591
|
var ServerReleaseStore = class {
|
|
27396
|
-
constructor(spec, installer, roots = frizzPaths(), compatibility = CURRENT_COMPATIBILITY) {
|
|
27592
|
+
constructor(spec, installer, roots = frizzPaths(), compatibility = CURRENT_COMPATIBILITY, minimum) {
|
|
27397
27593
|
this.spec = spec;
|
|
27398
27594
|
this.installer = installer;
|
|
27399
27595
|
this.roots = roots;
|
|
27400
27596
|
this.compatibility = compatibility;
|
|
27597
|
+
this.minimum = minimum;
|
|
27401
27598
|
assertSpec(spec, compatibility);
|
|
27402
27599
|
const key = createHash4("sha256").update(spec.package).digest("hex").slice(0, 16);
|
|
27403
27600
|
this.generations = join15(roots.cache, "server-releases", key);
|
|
@@ -27447,6 +27644,22 @@ var ServerReleaseStore = class {
|
|
|
27447
27644
|
this.markCompatibilityBeforeLaunch();
|
|
27448
27645
|
return generation;
|
|
27449
27646
|
}
|
|
27647
|
+
/**
|
|
27648
|
+
* The selected release, when it is older than this shell works with: `load` will start the pinned
|
|
27649
|
+
* one instead. Undefined for a machine with no selection, an unreadable one (`load` reports that),
|
|
27650
|
+
* and any selection at or above the minimum.
|
|
27651
|
+
*/
|
|
27652
|
+
belowMinimum() {
|
|
27653
|
+
if (this.minimum === void 0 || !existsSync9(this.selection)) return void 0;
|
|
27654
|
+
let selected;
|
|
27655
|
+
try {
|
|
27656
|
+
selected = record(readJson(this.selection)).version;
|
|
27657
|
+
} catch {
|
|
27658
|
+
return void 0;
|
|
27659
|
+
}
|
|
27660
|
+
if (typeof selected !== "string" || compareReleaseVersions(selected, this.minimum) !== -1) return void 0;
|
|
27661
|
+
return { selected, minimum: this.minimum, pinned: this.spec.version };
|
|
27662
|
+
}
|
|
27450
27663
|
async load() {
|
|
27451
27664
|
this.assertMarkerIsSupported();
|
|
27452
27665
|
if (!existsSync9(this.selection)) return this.prepareInitial(this.spec.version);
|
|
@@ -27460,6 +27673,7 @@ var ServerReleaseStore = class {
|
|
|
27460
27673
|
if (spec.dataEpoch < this.compatibility.dataEpoch) return this.prepareInitial(this.spec.version);
|
|
27461
27674
|
if (spec.dataEpoch > this.compatibility.dataEpoch)
|
|
27462
27675
|
throw new Error("this Frizz server requires a newer launcher or a data migration; restart with a compatible Frizz release");
|
|
27676
|
+
if (this.belowMinimum()) return this.prepareInitial(this.spec.version);
|
|
27463
27677
|
const root = this.packageRoot(selected.id);
|
|
27464
27678
|
const generation = !existsSync9(root) ? await this.prepare(spec.version) : validateServerGeneration(root, spec, selected.id, this.compatibility);
|
|
27465
27679
|
this.markCompatibilityBeforeLaunch();
|
|
@@ -27484,7 +27698,7 @@ var ServerReleaseStore = class {
|
|
|
27484
27698
|
}
|
|
27485
27699
|
}
|
|
27486
27700
|
commit(generation) {
|
|
27487
|
-
if (generation.package !== this.spec.package || generation.root !==
|
|
27701
|
+
if (generation.package !== this.spec.package || generation.root !== realpathSync7(this.packageRoot(generation.id)))
|
|
27488
27702
|
throw new Error("cannot select a Frizz server outside this release store");
|
|
27489
27703
|
validateServerGeneration(generation.root, generation, generation.id, this.compatibility);
|
|
27490
27704
|
const { package: packageName, version, protocol, dataEpoch, id } = generation;
|
|
@@ -27496,7 +27710,7 @@ var ServerReleaseStore = class {
|
|
|
27496
27710
|
init_frizz_paths();
|
|
27497
27711
|
init_project_launch();
|
|
27498
27712
|
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
27499
|
-
import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, realpathSync as
|
|
27713
|
+
import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, realpathSync as realpathSync8 } from "node:fs";
|
|
27500
27714
|
import { join as join16 } from "node:path";
|
|
27501
27715
|
var STABLE_SERVER_OWNER_NAME = "frizz-server";
|
|
27502
27716
|
var STABLE_SERVER_OWNER_PROJECT_ID = "c1fd5810-0f8a-4c1d-91a0-6d7445d28e5a";
|
|
@@ -27510,7 +27724,7 @@ function stableServerOwnerTarget(roots) {
|
|
|
27510
27724
|
mkdirSync13(resolved.data, { recursive: true, mode: 448 });
|
|
27511
27725
|
return {
|
|
27512
27726
|
projectId: STABLE_SERVER_OWNER_PROJECT_ID,
|
|
27513
|
-
projectDir:
|
|
27727
|
+
projectDir: realpathSync8(resolved.data),
|
|
27514
27728
|
stateDir: join16(resolved.state, STABLE_SERVER_OWNER_NAME)
|
|
27515
27729
|
};
|
|
27516
27730
|
}
|
|
@@ -27587,6 +27801,9 @@ function releaseHeldLease(lease) {
|
|
|
27587
27801
|
return true;
|
|
27588
27802
|
}
|
|
27589
27803
|
|
|
27804
|
+
// src/production.ts
|
|
27805
|
+
init_launch_routing();
|
|
27806
|
+
|
|
27590
27807
|
// src/preflight.ts
|
|
27591
27808
|
import { spawnSync } from "node:child_process";
|
|
27592
27809
|
var SUPPORTED_NODE_LINES = [
|
|
@@ -27713,7 +27930,13 @@ var serverOwner;
|
|
|
27713
27930
|
process.on("exit", () => {
|
|
27714
27931
|
if (serverOwner) releaseStableServerOwner(serverOwner);
|
|
27715
27932
|
});
|
|
27716
|
-
var
|
|
27933
|
+
var launchDir = (() => {
|
|
27934
|
+
try {
|
|
27935
|
+
return process.cwd();
|
|
27936
|
+
} catch {
|
|
27937
|
+
return homedir12();
|
|
27938
|
+
}
|
|
27939
|
+
})();
|
|
27717
27940
|
var workspace = (() => {
|
|
27718
27941
|
try {
|
|
27719
27942
|
if (!reexec) {
|
|
@@ -27725,21 +27948,26 @@ var workspace = (() => {
|
|
|
27725
27948
|
if (!pinned) throw new Error("registry successor is missing its pinned project identity");
|
|
27726
27949
|
return workspaceFromLaunchTarget(pinned);
|
|
27727
27950
|
}
|
|
27728
|
-
|
|
27729
|
-
launchIntent = intent;
|
|
27730
|
-
return intent.workspace;
|
|
27951
|
+
return hostWorkspace();
|
|
27731
27952
|
} catch (error) {
|
|
27732
27953
|
return fail(error);
|
|
27733
27954
|
}
|
|
27734
27955
|
})();
|
|
27735
|
-
|
|
27956
|
+
function runningBoardPort() {
|
|
27957
|
+
const machine = readStableServerOwner();
|
|
27958
|
+
if (machine.kind === "running") return machine.port;
|
|
27959
|
+
return liveWorkspaceOwner(workspace.stateDir, workspaceLaunchTarget(workspace))?.port;
|
|
27960
|
+
}
|
|
27961
|
+
function controlPorts() {
|
|
27962
|
+
const machine = readStableServerOwner();
|
|
27963
|
+
return [.../* @__PURE__ */ new Set([...machine.kind === "running" ? [machine.port] : [], DEFAULT_PORT, fallbackPort(DEFAULT_PORT)])];
|
|
27964
|
+
}
|
|
27736
27965
|
if (options.stop) {
|
|
27737
27966
|
try {
|
|
27738
|
-
const control = await resolveLaunchControlTarget({ stateDir: workspace.stateDir, target: workspaceLaunchTarget(workspace) });
|
|
27739
|
-
const where = control.host ? workspace.root : `${workspace.root} (the board was launched from ${control.target.projectDir})`;
|
|
27967
|
+
const control = await resolveLaunchControlTarget({ stateDir: workspace.stateDir, target: workspaceLaunchTarget(workspace), ports: controlPorts() });
|
|
27740
27968
|
const result = await stopProjectLaunch({ stateDir: control.stateDir, target: control.target });
|
|
27741
27969
|
console.log(
|
|
27742
|
-
result.kind !== "stopped" ?
|
|
27970
|
+
result.kind !== "stopped" ? "Frizz is not running" : result.stale ? "Frizz was not running; cleared the record a dead board left behind" : "stopped Frizz; running agents keep going \u2014 they are detached daemons"
|
|
27743
27971
|
);
|
|
27744
27972
|
process.exit(0);
|
|
27745
27973
|
} catch (error) {
|
|
@@ -27747,14 +27975,13 @@ if (options.stop) {
|
|
|
27747
27975
|
}
|
|
27748
27976
|
}
|
|
27749
27977
|
if (options.status) {
|
|
27750
|
-
const control = await resolveLaunchControlTarget({ stateDir: workspace.stateDir, target: workspaceLaunchTarget(workspace) });
|
|
27978
|
+
const control = await resolveLaunchControlTarget({ stateDir: workspace.stateDir, target: workspaceLaunchTarget(workspace), ports: controlPorts() });
|
|
27751
27979
|
const running = await runningFrizzStatus({ stateDir: control.stateDir, target: control.target });
|
|
27752
27980
|
if (!running) {
|
|
27753
|
-
console.log(
|
|
27981
|
+
console.log("Frizz is not running");
|
|
27754
27982
|
process.exit(1);
|
|
27755
27983
|
}
|
|
27756
27984
|
console.log(`running: http://127.0.0.1:${running.port}`);
|
|
27757
|
-
console.log(`workspace: ${workspace.root}`);
|
|
27758
27985
|
if (!control.host) console.log(`launched from: ${control.target.projectDir} (one server serves every project)`);
|
|
27759
27986
|
console.log(`supervisor pid: ${running.pid}`);
|
|
27760
27987
|
console.log(`version: ${running.version ?? "unknown"}`);
|
|
@@ -27762,23 +27989,23 @@ if (options.status) {
|
|
|
27762
27989
|
process.exit(0);
|
|
27763
27990
|
}
|
|
27764
27991
|
if (options.sessions || options.signOut !== void 0) {
|
|
27765
|
-
const
|
|
27766
|
-
if (!
|
|
27767
|
-
console.error("frizz:
|
|
27992
|
+
const port = runningBoardPort();
|
|
27993
|
+
if (!port) {
|
|
27994
|
+
console.error("frizz: Frizz is not running");
|
|
27768
27995
|
process.exit(1);
|
|
27769
27996
|
}
|
|
27770
|
-
if (options.signOut !== void 0) await signOutSession(
|
|
27771
|
-
await listSessions(
|
|
27997
|
+
if (options.signOut !== void 0) await signOutSession(port, options.signOut);
|
|
27998
|
+
await listSessions(port);
|
|
27772
27999
|
}
|
|
27773
28000
|
if (options.link) {
|
|
27774
|
-
const
|
|
27775
|
-
if (!
|
|
27776
|
-
console.error("frizz:
|
|
28001
|
+
const port = runningBoardPort();
|
|
28002
|
+
if (!port) {
|
|
28003
|
+
console.error("frizz: Frizz is not running");
|
|
27777
28004
|
process.exit(1);
|
|
27778
28005
|
}
|
|
27779
|
-
const response = await fetch(`http://127.0.0.1:${
|
|
28006
|
+
const response = await fetch(`http://127.0.0.1:${port}${SUPERVISOR_ACCESS_CODE_PATH}`, {
|
|
27780
28007
|
method: "POST",
|
|
27781
|
-
headers: { origin: `http://127.0.0.1:${
|
|
28008
|
+
headers: { origin: `http://127.0.0.1:${port}` }
|
|
27782
28009
|
}).catch(() => void 0);
|
|
27783
28010
|
if (!response?.ok) {
|
|
27784
28011
|
const detail = response ? (await response.json().catch(() => void 0))?.error : void 0;
|
|
@@ -27802,7 +28029,7 @@ readout?.plan([
|
|
|
27802
28029
|
{ key: "browser", label: options.noApp ? "Address" : "Browser" }
|
|
27803
28030
|
]);
|
|
27804
28031
|
readout?.begin("server", "starting");
|
|
27805
|
-
logger.info("launcher", `Frizz launcher ${PACKAGE_VERSION} starting
|
|
28032
|
+
logger.info("launcher", `Frizz launcher ${PACKAGE_VERSION} starting in ${launchDir}`);
|
|
27806
28033
|
var target = workspaceLaunchTarget(workspace);
|
|
27807
28034
|
var expected = expectedOwnerHealth(target, readProjectLaunchOwner(workspace.stateDir));
|
|
27808
28035
|
async function existingPort() {
|
|
@@ -27811,39 +28038,54 @@ async function existingPort() {
|
|
|
27811
28038
|
for (const port of new Set(ports)) if (await probeFrizz(port, expected)) return port;
|
|
27812
28039
|
return void 0;
|
|
27813
28040
|
}
|
|
27814
|
-
|
|
28041
|
+
var legacyIntentCache;
|
|
28042
|
+
function legacyIntent() {
|
|
28043
|
+
return legacyIntentCache ??= resolveLaunchIntent(launchDir);
|
|
28044
|
+
}
|
|
28045
|
+
function legacyProject() {
|
|
28046
|
+
const intent = legacyIntent();
|
|
28047
|
+
if (intent.kind !== "open") return void 0;
|
|
28048
|
+
const { id, root, name } = intent.workspace;
|
|
27815
28049
|
try {
|
|
27816
|
-
|
|
27817
|
-
|
|
27818
|
-
homedir11()
|
|
27819
|
-
).entry?.slug;
|
|
28050
|
+
const slug = registerProject({ dir: root, id, remoteOwner: resolveProjectLabel(root)?.split("/")[0] }, homedir12()).entry?.slug;
|
|
28051
|
+
return slug ? { id, root, name, slug } : void 0;
|
|
27820
28052
|
} catch {
|
|
27821
28053
|
return void 0;
|
|
27822
28054
|
}
|
|
27823
28055
|
}
|
|
27824
|
-
|
|
27825
|
-
|
|
27826
|
-
|
|
27827
|
-
if (
|
|
27828
|
-
|
|
27829
|
-
|
|
27830
|
-
|
|
27831
|
-
|
|
27832
|
-
}
|
|
28056
|
+
function legacyLanding() {
|
|
28057
|
+
try {
|
|
28058
|
+
const intent = legacyIntent();
|
|
28059
|
+
if (intent.kind === "grid") return { path: "/" };
|
|
28060
|
+
if (intent.kind === "offer") return { path: offerPath(intent.directory) };
|
|
28061
|
+
const project = legacyProject();
|
|
28062
|
+
return project ? { path: `/project/${project.slug}`, project: { name: project.name, dir: project.root } } : { path: "/" };
|
|
28063
|
+
} catch (error) {
|
|
28064
|
+
return { path: "/", notice: `Could not open ${launchDir}: ${error instanceof Error ? error.message : String(error)}` };
|
|
27833
28065
|
}
|
|
27834
|
-
return cachedSlugPath;
|
|
27835
28066
|
}
|
|
27836
|
-
async function
|
|
27837
|
-
const
|
|
27838
|
-
if (
|
|
28067
|
+
async function resolveLanding(port, ownerToken) {
|
|
28068
|
+
const answered = ownerToken ? await askLaunchRoute(port, ownerToken, launchDir) : void 0;
|
|
28069
|
+
if (answered) return answered;
|
|
28070
|
+
logger.info("launcher", "the running server did not say where this launch lands; using this launcher's own rules");
|
|
28071
|
+
return legacyLanding();
|
|
28072
|
+
}
|
|
28073
|
+
async function joinPreSplitFrizz() {
|
|
27839
28074
|
for (const port of /* @__PURE__ */ new Set([DEFAULT_PORT, fallbackPort(DEFAULT_PORT)])) {
|
|
27840
|
-
if (await
|
|
27841
|
-
|
|
28075
|
+
if (!await readFrizzHealth(port)) continue;
|
|
28076
|
+
let project;
|
|
28077
|
+
try {
|
|
28078
|
+
project = legacyProject();
|
|
28079
|
+
} catch {
|
|
28080
|
+
return void 0;
|
|
28081
|
+
}
|
|
28082
|
+
if (!project) return void 0;
|
|
28083
|
+
if (await probeFrizz(port, { projectId: project.id, projectDir: project.root, slug: project.slug })) return port;
|
|
27842
28084
|
}
|
|
27843
28085
|
return void 0;
|
|
27844
28086
|
}
|
|
27845
|
-
async function openOrPrint(port, reused,
|
|
27846
|
-
const url = `http://127.0.0.1:${port}${path}`;
|
|
28087
|
+
async function openOrPrint(port, reused, landing) {
|
|
28088
|
+
const url = `http://127.0.0.1:${port}${landing.path}`;
|
|
27847
28089
|
logger.info("launcher", `${reused ? "reusing" : "started"} Frizz at ${url}`);
|
|
27848
28090
|
readout?.settle("server", "done", reused ? `already running on port ${port}` : `port ${port}`);
|
|
27849
28091
|
let browser;
|
|
@@ -27870,26 +28112,27 @@ async function openOrPrint(port, reused, path = "") {
|
|
|
27870
28112
|
const warnings = [];
|
|
27871
28113
|
if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
|
|
27872
28114
|
if (sessionNotice && !reused) warnings.push(sessionNotice);
|
|
28115
|
+
if (landing.notice) warnings.push(landing.notice);
|
|
27873
28116
|
if (!readout) {
|
|
27874
|
-
console.log(`${reused ? "reusing" : "started"} Frizz${
|
|
28117
|
+
console.log(`${reused ? "reusing" : "started"} Frizz${landing.project ? ` for ${landing.project.dir}` : ""}`);
|
|
27875
28118
|
console.log(url);
|
|
27876
28119
|
if (publicOrigin) console.log(activeAccessLink?.url ?? `${publicOrigin}/`);
|
|
27877
28120
|
for (const warning of warnings) console.log(warning);
|
|
27878
28121
|
return;
|
|
27879
28122
|
}
|
|
27880
|
-
const home =
|
|
28123
|
+
const home = homedir12();
|
|
27881
28124
|
readout.ready(
|
|
27882
28125
|
[
|
|
27883
28126
|
{ label: "Local", value: boardAddress(url), accent: true },
|
|
27884
28127
|
...publicOrigin ? [{ label: "Public", value: activeAccessLink?.url ?? `${publicOrigin}/`, accent: true }] : [],
|
|
27885
|
-
// Only
|
|
27886
|
-
//
|
|
27887
|
-
...
|
|
28128
|
+
// Only a launch that OPENED a project names one: the dashboard and an offer are about no
|
|
28129
|
+
// project, and the folder the server runs on is Frizz's own.
|
|
28130
|
+
...landing.project ? [{ label: "Project", value: `${landing.project.name} \u2014 ${tildePath(landing.project.dir, home)}` }] : [],
|
|
27888
28131
|
...logger.file ? [{ label: "Logs", value: tildePath(logger.file, home) }] : []
|
|
27889
28132
|
],
|
|
27890
28133
|
reused ? (
|
|
27891
28134
|
// This launch owns nothing and exits immediately, so ctrl-c would not stop what it reopened.
|
|
27892
|
-
"reopened the
|
|
28135
|
+
"reopened the Frizz already running on this machine \xB7 stop it from the terminal that started it"
|
|
27893
28136
|
) : options.debug ? void 0 : publicOrigin ? "press L for a fresh sign-in link \xB7 R to change how this board is reached \xB7 ctrl-c to stop" : "press R to reach this board from a phone or another machine \xB7 ctrl-c to stop",
|
|
27894
28137
|
{
|
|
27895
28138
|
...reused ? { status: `already running on port ${port}` } : {},
|
|
@@ -27914,32 +28157,49 @@ async function runSupervisor(port, token, onPrepared = () => {
|
|
|
27914
28157
|
target,
|
|
27915
28158
|
owner.token
|
|
27916
28159
|
);
|
|
27917
|
-
const
|
|
27918
|
-
|
|
27919
|
-
process.env.FRIZZ_SERVER_PACKAGE
|
|
27920
|
-
);
|
|
28160
|
+
const manifest = JSON.parse(readFileSync14(join17(import.meta.dirname, "..", "package.json"), "utf8"));
|
|
28161
|
+
const spec = serverReleaseSpec(manifest, process.env.FRIZZ_SERVER_PACKAGE);
|
|
27921
28162
|
const installer = npmServerPackageInstaller(env);
|
|
27922
|
-
const store = new ServerReleaseStore(spec, installer);
|
|
27923
|
-
|
|
28163
|
+
const store = new ServerReleaseStore(spec, installer, void 0, void 0, serverReleaseMinimum(manifest));
|
|
28164
|
+
const raise = store.belowMinimum();
|
|
28165
|
+
if (raise)
|
|
28166
|
+
logger.info("launcher", `the installed Frizz server ${raise.selected} is older than this launcher works with (${raise.minimum}); starting on ${raise.pinned}`);
|
|
28167
|
+
activityReadout?.notice(
|
|
28168
|
+
"progress",
|
|
28169
|
+
"Starting",
|
|
28170
|
+
raise ? `installing Frizz server ${raise.pinned}: ${raise.selected} is older than this launcher works with` : "loading the selected Frizz server release"
|
|
28171
|
+
);
|
|
27924
28172
|
let active = await store.load();
|
|
27925
28173
|
let pending;
|
|
27926
28174
|
let previous = active;
|
|
27927
28175
|
onPrepared();
|
|
27928
28176
|
let updateAvailable = false;
|
|
27929
28177
|
let updateVersion;
|
|
28178
|
+
let probeFailure;
|
|
27930
28179
|
const refreshUpdateAvailable = async () => {
|
|
27931
28180
|
try {
|
|
27932
28181
|
const checkedVersion = active.version;
|
|
27933
28182
|
const plan = await planRegistryUpdate(spec.package, checkedVersion, installer);
|
|
28183
|
+
probeFailure = void 0;
|
|
27934
28184
|
if (active.version !== checkedVersion) return;
|
|
27935
28185
|
updateAvailable = plan !== null;
|
|
27936
28186
|
updateVersion = plan?.latestVersion;
|
|
27937
|
-
} catch {
|
|
28187
|
+
} catch (error) {
|
|
28188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28189
|
+
if (message !== probeFailure) logger.warn("launcher", `could not check npm for a newer Frizz server: ${message}`);
|
|
28190
|
+
probeFailure = message;
|
|
27938
28191
|
}
|
|
27939
28192
|
};
|
|
27940
28193
|
void refreshUpdateAvailable();
|
|
27941
28194
|
const updateAvailablePoll = setInterval(() => void refreshUpdateAvailable(), 30 * 60 * 1e3);
|
|
27942
28195
|
updateAvailablePoll.unref();
|
|
28196
|
+
try {
|
|
28197
|
+
const projects = dirname14(workspace.stateDir);
|
|
28198
|
+
const adopted = adoptSessionState(workspace.stateDir, readdirSync4(projects).map((name) => join17(projects, name)));
|
|
28199
|
+
if (adopted) logger.info("launcher", `carried the remote sign-in state over from ${adopted}`);
|
|
28200
|
+
} catch (error) {
|
|
28201
|
+
logger.warn("launcher", `could not carry the remote sign-in state over: ${error instanceof Error ? error.message : String(error)}`);
|
|
28202
|
+
}
|
|
27943
28203
|
const sessions = loadSessionState(workspace.stateDir);
|
|
27944
28204
|
const epochRecord = sessionEpochLogLine(sessions.advance, sessions.signedOut);
|
|
27945
28205
|
if (epochRecord) logger.warn("launcher", epochRecord);
|
|
@@ -28043,7 +28303,7 @@ async function runSupervisor(port, token, onPrepared = () => {
|
|
|
28043
28303
|
paneHost?.dispose();
|
|
28044
28304
|
remote?.stop();
|
|
28045
28305
|
const suffix = logger.file ? `
|
|
28046
|
-
log: ${tildePath(logger.file,
|
|
28306
|
+
log: ${tildePath(logger.file, homedir12())}` : "";
|
|
28047
28307
|
process.stdout.write(
|
|
28048
28308
|
code === 0 ? `
|
|
28049
28309
|
Frizz stopped. Running agents keep going \u2014 they are detached daemons.${suffix}
|
|
@@ -28080,7 +28340,7 @@ try {
|
|
|
28080
28340
|
if (globalClaim.kind === "running") {
|
|
28081
28341
|
if (options.port && options.port !== globalClaim.port)
|
|
28082
28342
|
throw new Error(`Frizz is already running on port ${globalClaim.port}; --port cannot change a running server`);
|
|
28083
|
-
await openOrPrint(globalClaim.port, true,
|
|
28343
|
+
await openOrPrint(globalClaim.port, true, await resolveLanding(globalClaim.port, globalClaim.owner.token));
|
|
28084
28344
|
process.exit(0);
|
|
28085
28345
|
}
|
|
28086
28346
|
serverOwner = globalClaim.lease;
|
|
@@ -28092,16 +28352,16 @@ try {
|
|
|
28092
28352
|
}
|
|
28093
28353
|
const existing = await existingPort();
|
|
28094
28354
|
if (existing) {
|
|
28095
|
-
await openOrPrint(existing, true,
|
|
28355
|
+
await openOrPrint(existing, true, await resolveLanding(existing, readProjectLaunchOwner(workspace.stateDir)?.token));
|
|
28096
28356
|
process.exit(0);
|
|
28097
28357
|
}
|
|
28098
|
-
const
|
|
28099
|
-
if (
|
|
28100
|
-
await openOrPrint(
|
|
28358
|
+
const preSplit = await joinPreSplitFrizz();
|
|
28359
|
+
if (preSplit) {
|
|
28360
|
+
await openOrPrint(preSplit, true, legacyLanding());
|
|
28101
28361
|
process.exit(0);
|
|
28102
28362
|
}
|
|
28103
28363
|
const claim = tryAcquireProjectLaunchOwner(target, "launcher");
|
|
28104
|
-
if (claim.kind !== "acquired") throw new Error("Frizz is starting
|
|
28364
|
+
if (claim.kind !== "acquired") throw new Error("Frizz is starting on this machine; retry shortly");
|
|
28105
28365
|
let release = await acquireGlobalLaunchLock();
|
|
28106
28366
|
let portReservation;
|
|
28107
28367
|
try {
|
|
@@ -28127,7 +28387,7 @@ try {
|
|
|
28127
28387
|
]);
|
|
28128
28388
|
portReservation();
|
|
28129
28389
|
portReservation = void 0;
|
|
28130
|
-
await openOrPrint(port, false,
|
|
28390
|
+
await openOrPrint(port, false, await resolveLanding(port, serverOwner.token));
|
|
28131
28391
|
await running;
|
|
28132
28392
|
} finally {
|
|
28133
28393
|
release?.();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frizz",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.13",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A local web UI for running many coding agents at once",
|
|
6
6
|
"keywords": [
|
|
@@ -66,7 +66,8 @@
|
|
|
66
66
|
},
|
|
67
67
|
"frizzServer": {
|
|
68
68
|
"package": "frizz-server",
|
|
69
|
-
"version": "0.15.
|
|
69
|
+
"version": "0.15.23",
|
|
70
|
+
"minimum": "0.15.21",
|
|
70
71
|
"protocol": 1,
|
|
71
72
|
"dataEpoch": 1
|
|
72
73
|
}
|