frizz 0.13.5 → 0.13.6
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/dist/frizz.js +131 -10
- package/package.json +2 -2
package/dist/frizz.js
CHANGED
|
@@ -12493,7 +12493,7 @@ function requireEffortOutsideAcp(profile, ctx) {
|
|
|
12493
12493
|
function fallbackPort(base) {
|
|
12494
12494
|
return base + 1e4;
|
|
12495
12495
|
}
|
|
12496
|
-
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, 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, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, 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, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, 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;
|
|
12496
|
+
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, 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, 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, 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, ThreadPluginReloadResult, 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;
|
|
12497
12497
|
var init_src = __esm({
|
|
12498
12498
|
"packages/shared/src/index.ts"() {
|
|
12499
12499
|
"use strict";
|
|
@@ -12907,12 +12907,12 @@ var init_src = __esm({
|
|
|
12907
12907
|
" if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
|
|
12908
12908
|
" **bolded verb phrase**.",
|
|
12909
12909
|
"- `` ```awaiting `` \u2014 you are WAITING on work that is actually running. FRONTMATTER, THEN MARKDOWN:",
|
|
12910
|
-
" one
|
|
12910
|
+
" one YAML list per kind of thing you are waiting on, a REQUIRED `for:` duration, then a `---` line and",
|
|
12911
12911
|
" as much prose as you want. The prose is OPTIONAL; the lines above it are not.",
|
|
12912
12912
|
"",
|
|
12913
12913
|
" ```awaiting",
|
|
12914
|
-
"
|
|
12915
|
-
"
|
|
12914
|
+
" shells: [<the id your runtime gave you>]",
|
|
12915
|
+
" prs: [owner/repo#123]",
|
|
12916
12916
|
" for: 2h",
|
|
12917
12917
|
" ---",
|
|
12918
12918
|
" What you are waiting for, in your own words \u2014 this is what the human reads on your card.",
|
|
@@ -13307,6 +13307,11 @@ var init_src = __esm({
|
|
|
13307
13307
|
UpsertOwnLinkResult = external_exports.object({ link: ThreadLinkView }).strict();
|
|
13308
13308
|
DropOwnLinkInput = external_exports.object({ slug: ThreadSlug, id: external_exports.string().min(1) }).strict();
|
|
13309
13309
|
DropOwnLinkResult = external_exports.object({ dropped: external_exports.boolean() }).strict();
|
|
13310
|
+
LiveTool = external_exports.object({
|
|
13311
|
+
name: external_exports.string(),
|
|
13312
|
+
desc: external_exports.string().optional(),
|
|
13313
|
+
detail: external_exports.string().optional()
|
|
13314
|
+
});
|
|
13310
13315
|
ThreadView = external_exports.object({
|
|
13311
13316
|
id: ThreadSlug,
|
|
13312
13317
|
// slug; filename is <slug>.md
|
|
@@ -13345,6 +13350,15 @@ var init_src = __esm({
|
|
|
13345
13350
|
// user hid the row from the nav; respawn/resume un-archives
|
|
13346
13351
|
lastAssistant: external_exports.string().optional(),
|
|
13347
13352
|
// trimmed preview of last assistant text
|
|
13353
|
+
// The FIRST non-empty line of that same text, markdown intact and newlines honoured (capped) — the
|
|
13354
|
+
// handoff's verdict line ("**Fixed** — …"), which `lastAssistant` cannot give back because its preview
|
|
13355
|
+
// collapses every newline to a space. The phone board's rested row reads it. Optional so old
|
|
13356
|
+
// snapshots parse.
|
|
13357
|
+
lastAssistantLine: external_exports.string().optional(),
|
|
13358
|
+
// The newest tool call the agent has issued and not yet had a result for (Claude session threads),
|
|
13359
|
+
// in the shape `toolActivityLabel` reads, so a list row can say "Running the focused tests" with the
|
|
13360
|
+
// gerund the chat's working indicator shows. Absent between calls, at rest, and for other backends.
|
|
13361
|
+
liveTool: LiveTool.optional(),
|
|
13348
13362
|
spawnedAt: external_exports.string().optional(),
|
|
13349
13363
|
// ISO8601
|
|
13350
13364
|
lastActivityAt: external_exports.string().optional(),
|
|
@@ -13403,6 +13417,11 @@ var init_src = __esm({
|
|
|
13403
13417
|
* same card and the swap is invisible. Absent whenever there is nothing in flight, which is almost
|
|
13404
13418
|
* always — it exists for the seconds between the human sending and the worker being handed it. */
|
|
13405
13419
|
answersInFlight: external_exports.string().optional(),
|
|
13420
|
+
/** The human's message — a follow-up or a registered answer — is on its way to the worker and the turn
|
|
13421
|
+
* has not started yet (board.deriveDeliveryInFlight). The queue has already let the thread go; this
|
|
13422
|
+
* tells the rail to draw it as working in every tab, not only the one that sent it (web
|
|
13423
|
+
* lib/steering.ts). Absent otherwise. */
|
|
13424
|
+
deliveryInFlight: external_exports.boolean().optional(),
|
|
13406
13425
|
// ISO8601 of the newest REAL user interaction (answer/steer/dispatch) — the chronological listing
|
|
13407
13426
|
// sort key. Optional; the listing falls back to spawnedAt when absent (a dispatch IS an interaction).
|
|
13408
13427
|
lastUserAt: external_exports.string().optional(),
|
|
@@ -13835,8 +13854,18 @@ var init_src = __esm({
|
|
|
13835
13854
|
// Broker-backed Claude only — that is every Claude thread dispatched since the broker cutover. On
|
|
13836
13855
|
// any other runtime the message is delivered normally and this is ignored, never refused: a send
|
|
13837
13856
|
// that arrives is always better than a send that errors.
|
|
13838
|
-
interrupt: external_exports.boolean().optional()
|
|
13857
|
+
interrupt: external_exports.boolean().optional(),
|
|
13858
|
+
// The deliveryId of a FAILED send this one re-sends — the failed bubble's Retry. The server drops that
|
|
13859
|
+
// failed entry in the same write that opens this send's own, so the retry replaces the failed bubble
|
|
13860
|
+
// rather than standing beside it. A new deliveryId, never the old one: the old send's failure may be
|
|
13861
|
+
// ambiguous, and only the operator decides to risk a second copy.
|
|
13862
|
+
supersedes: external_exports.string().min(1).max(200).optional()
|
|
13839
13863
|
});
|
|
13864
|
+
DismissFailedFollowUpInput = external_exports.object({
|
|
13865
|
+
slug: ThreadSlug,
|
|
13866
|
+
deliveryId: external_exports.string().min(1).max(200)
|
|
13867
|
+
}).strict();
|
|
13868
|
+
DismissFailedFollowUpResult = external_exports.object({ dismissed: external_exports.boolean() }).strict();
|
|
13840
13869
|
UnqueueFollowUpInput = external_exports.object({
|
|
13841
13870
|
slug: ThreadSlug,
|
|
13842
13871
|
// Same staleness guard as followUp: a stale tab must not unqueue against a re-dispatched session.
|
|
@@ -13856,6 +13885,11 @@ var init_src = __esm({
|
|
|
13856
13885
|
interrupted: external_exports.boolean(),
|
|
13857
13886
|
reason: external_exports.string().optional()
|
|
13858
13887
|
}).strict();
|
|
13888
|
+
CompactThreadInput = external_exports.object({
|
|
13889
|
+
slug: ThreadSlug,
|
|
13890
|
+
// Same staleness guard as followUp: a stale tab must not compact a re-dispatched session.
|
|
13891
|
+
sessionId: external_exports.string().min(1)
|
|
13892
|
+
}).strict();
|
|
13859
13893
|
SetThreadSnoozeInput = external_exports.object({
|
|
13860
13894
|
slug: ThreadSlug,
|
|
13861
13895
|
sessionId: external_exports.string().min(1),
|
|
@@ -14464,8 +14498,13 @@ ${DISPATCH_TASK_BANNER}
|
|
|
14464
14498
|
// yet — renders as an ordinary (un-grayed) user bubble. "unconfirmed": no evidence appeared within
|
|
14465
14499
|
// the timeout — the injection likely mutated/never landed; the client renders a quiet warning.
|
|
14466
14500
|
// Once the real transcript record lands the ledger drops the item and this field goes with it.
|
|
14467
|
-
//
|
|
14468
|
-
|
|
14501
|
+
// "sending": the server's WRITE-AHEAD entry, opened before any transport is touched, so the text is
|
|
14502
|
+
// the server's from the instant it arrives. "failed": the transport threw, or never answered — the
|
|
14503
|
+
// text is kept until the operator retries, edits or dismisses it, and nothing retries it on its own.
|
|
14504
|
+
// Additive + optional: an older client renders both as a plain bubble (gray for "sending").
|
|
14505
|
+
deliveryState: external_exports.enum(["sending", "pending", "enqueued", "delivered", "unconfirmed", "failed"]).optional(),
|
|
14506
|
+
// Why a "failed" send failed, verbatim from the transport. Set only alongside deliveryState "failed".
|
|
14507
|
+
deliveryError: external_exports.string().optional(),
|
|
14469
14508
|
// FRIZZ wrote this user turn, not the human: it is a scheduler wake delivery (isWakeDelivery). The
|
|
14470
14509
|
// client renders it as a first-party card rather than the human's off-white right-justified bubble,
|
|
14471
14510
|
// which was claiming the operator had typed a message the watcher composed. Additive + optional: an
|
|
@@ -14617,7 +14656,8 @@ ${DISPATCH_TASK_BANNER}
|
|
|
14617
14656
|
});
|
|
14618
14657
|
ProjectRailCounts = external_exports.object({
|
|
14619
14658
|
queued: external_exports.number().int().nonnegative(),
|
|
14620
|
-
running: external_exports.number().int().nonnegative()
|
|
14659
|
+
running: external_exports.number().int().nonnegative(),
|
|
14660
|
+
asks: external_exports.number().int().nonnegative()
|
|
14621
14661
|
});
|
|
14622
14662
|
PROJECT_ICON_MAX_BASE64_CHARS = 4 * 1024 * 1024;
|
|
14623
14663
|
DirectoryPickResult = external_exports.discriminatedUnion("kind", [
|
|
@@ -20558,6 +20598,29 @@ var AccessStore = class _AccessStore {
|
|
|
20558
20598
|
const id = _AccessStore.idOf(payload);
|
|
20559
20599
|
return !(id && this.sessions.isRevoked(id));
|
|
20560
20600
|
}
|
|
20601
|
+
/**
|
|
20602
|
+
* Sign out the device HOLDING this session — the "Sign out this device" row, not `--sign-out`.
|
|
20603
|
+
*
|
|
20604
|
+
* The id comes from the session itself, and only after its signature verifies, so the one thing this
|
|
20605
|
+
* can ever revoke is the credential the caller just proved it holds. There is deliberately no
|
|
20606
|
+
* parameter naming any other id: signing out SOMEONE ELSE stays loopback-only, because a stolen
|
|
20607
|
+
* session must not be able to evict the owner and keep the board.
|
|
20608
|
+
*
|
|
20609
|
+
* - `revoked`: the id is on the denylist now, so a surviving copy of this cookie is refused too.
|
|
20610
|
+
* - `legacy`: a session minted before ids existed verifies but has no id to revoke on its own. The
|
|
20611
|
+
* caller can still clear the cookie; rotating the key remains the way to kill a copy of it.
|
|
20612
|
+
* - `no-session`: nothing valid was presented — absent, forged, expired, or already signed out.
|
|
20613
|
+
*/
|
|
20614
|
+
signOutSession(session) {
|
|
20615
|
+
if (!session || !this.verifySession(session)) return { result: "no-session" };
|
|
20616
|
+
const id = _AccessStore.idOf(session.slice(0, session.lastIndexOf(".")));
|
|
20617
|
+
if (!id) return { result: "legacy" };
|
|
20618
|
+
if (!this.sessions.revoke(id)) {
|
|
20619
|
+
this.sessions.record({ id, label: "unknown device", createdAt: this.options.now() });
|
|
20620
|
+
this.sessions.revoke(id);
|
|
20621
|
+
}
|
|
20622
|
+
return this.sessions.isRevoked(id) ? { result: "revoked", id } : { result: "no-session" };
|
|
20623
|
+
}
|
|
20561
20624
|
/**
|
|
20562
20625
|
* Drop codes past their expiry so a long-lived board does not accumulate them forever.
|
|
20563
20626
|
*
|
|
@@ -22583,6 +22646,7 @@ var SUPERVISOR_UPDATE_RESTART_PATH = `${SUPERVISOR_CONTROL_PREFIX}/update-restar
|
|
|
22583
22646
|
var SUPERVISOR_STATUS_PATH = `${SUPERVISOR_CONTROL_PREFIX}/status`;
|
|
22584
22647
|
var SUPERVISOR_ACCESS_CODE_PATH = `${SUPERVISOR_CONTROL_PREFIX}/access-code`;
|
|
22585
22648
|
var SUPERVISOR_SESSIONS_PATH = `${SUPERVISOR_CONTROL_PREFIX}/sessions`;
|
|
22649
|
+
var SUPERVISOR_SIGN_OUT_PATH = `${SUPERVISOR_CONTROL_PREFIX}/sign-out`;
|
|
22586
22650
|
var SUPERVISOR_CONTROL_PROTOCOL = 1;
|
|
22587
22651
|
function responseJson(res, status, value) {
|
|
22588
22652
|
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
@@ -22611,7 +22675,7 @@ function readCookie(header, name) {
|
|
|
22611
22675
|
}
|
|
22612
22676
|
function isControlRequest(req) {
|
|
22613
22677
|
const url = new URL(req.url ?? "/", "http://frizz.invalid");
|
|
22614
|
-
return url.pathname === SUPERVISOR_RESTART_PATH || url.pathname === SUPERVISOR_UPDATE_RESTART_PATH || url.pathname === SUPERVISOR_STATUS_PATH || url.pathname === SUPERVISOR_ACCESS_CODE_PATH || url.pathname === SUPERVISOR_SESSIONS_PATH;
|
|
22678
|
+
return url.pathname === SUPERVISOR_RESTART_PATH || url.pathname === SUPERVISOR_UPDATE_RESTART_PATH || url.pathname === SUPERVISOR_STATUS_PATH || url.pathname === SUPERVISOR_ACCESS_CODE_PATH || url.pathname === SUPERVISOR_SESSIONS_PATH || url.pathname === SUPERVISOR_SIGN_OUT_PATH;
|
|
22615
22679
|
}
|
|
22616
22680
|
function isLocalImageRequest(req) {
|
|
22617
22681
|
if (req.method !== "GET" && req.method !== "HEAD") return false;
|
|
@@ -22817,6 +22881,11 @@ var RestartSupervisorProxy = class {
|
|
|
22817
22881
|
}
|
|
22818
22882
|
async handleControl(req, res) {
|
|
22819
22883
|
const pathname = new URL(req.url ?? "/", "http://frizz.invalid").pathname;
|
|
22884
|
+
if (pathname !== SUPERVISOR_SIGN_OUT_PATH && this.arrivedPublicly(req) && !this.sessionAccepted(req)) {
|
|
22885
|
+
res.writeHead(401, { "cache-control": "no-store" });
|
|
22886
|
+
res.end();
|
|
22887
|
+
return;
|
|
22888
|
+
}
|
|
22820
22889
|
const sameOrigin = req.headers["sec-fetch-site"] === "same-origin" || this.vouchesSameOrigin(req);
|
|
22821
22890
|
const allowMissingOrigin = pathname === SUPERVISOR_STATUS_PATH && req.method === "GET" && sameOrigin;
|
|
22822
22891
|
if (!this.authorityAccepted(req, allowMissingOrigin)) {
|
|
@@ -22880,8 +22949,24 @@ var RestartSupervisorProxy = class {
|
|
|
22880
22949
|
responseJson(res, 200, { signedOut: 1 });
|
|
22881
22950
|
return;
|
|
22882
22951
|
}
|
|
22952
|
+
if (pathname === SUPERVISOR_SIGN_OUT_PATH) {
|
|
22953
|
+
if (req.method !== "POST") {
|
|
22954
|
+
res.writeHead(405, { allow: "POST" });
|
|
22955
|
+
res.end();
|
|
22956
|
+
return;
|
|
22957
|
+
}
|
|
22958
|
+
this.signOutThisDevice(req, res);
|
|
22959
|
+
return;
|
|
22960
|
+
}
|
|
22883
22961
|
if (pathname === SUPERVISOR_STATUS_PATH && req.method === "GET") {
|
|
22884
|
-
responseJson(res, 200, {
|
|
22962
|
+
responseJson(res, 200, {
|
|
22963
|
+
protocol: SUPERVISOR_CONTROL_PROTOCOL,
|
|
22964
|
+
...this.status(),
|
|
22965
|
+
// Sent ONLY when true, so a loopback tab's payload is byte-identical to what it was. The web
|
|
22966
|
+
// app cannot tell on its own: the session cookie is HttpOnly, and the page is the same bundle
|
|
22967
|
+
// whichever way it was reached. It is a fact about THIS request, never about the board.
|
|
22968
|
+
...this.carriesRemoteSession(req) ? { remoteSession: true } : {}
|
|
22969
|
+
});
|
|
22885
22970
|
return;
|
|
22886
22971
|
}
|
|
22887
22972
|
if (pathname !== SUPERVISOR_RESTART_PATH && pathname !== SUPERVISOR_UPDATE_RESTART_PATH || req.method !== "POST") {
|
|
@@ -22959,6 +23044,42 @@ var RestartSupervisorProxy = class {
|
|
|
22959
23044
|
if (!this.publicOrigin) return true;
|
|
22960
23045
|
return this.access.verifySession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
22961
23046
|
}
|
|
23047
|
+
/** Did this request arrive through the public origin carrying a live session? Loopback never does. */
|
|
23048
|
+
carriesRemoteSession(req) {
|
|
23049
|
+
return this.arrivedPublicly(req) && this.access.verifySession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
23050
|
+
}
|
|
23051
|
+
/**
|
|
23052
|
+
* End the session this request carries, and nothing else — see SUPERVISOR_SIGN_OUT_PATH in
|
|
23053
|
+
* handleControl for why that makes it safe to reach from the tunnel.
|
|
23054
|
+
*
|
|
23055
|
+
* A loopback request is a no-op even if it somehow carries a cookie: the operator's own tab has no
|
|
23056
|
+
* remote session to end, and loopback is the place `--sign-out` already covers.
|
|
23057
|
+
*/
|
|
23058
|
+
signOutThisDevice(req, res) {
|
|
23059
|
+
const reply = (status, body, clearCookie) => {
|
|
23060
|
+
res.writeHead(status, {
|
|
23061
|
+
"content-type": "application/json; charset=utf-8",
|
|
23062
|
+
"cache-control": "no-store",
|
|
23063
|
+
// Same attributes the exchange set, so the browser matches and drops that exact cookie.
|
|
23064
|
+
...clearCookie ? { "set-cookie": `${SESSION_COOKIE}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0` } : {}
|
|
23065
|
+
});
|
|
23066
|
+
res.end(JSON.stringify(body));
|
|
23067
|
+
};
|
|
23068
|
+
if (!this.arrivedPublicly(req)) {
|
|
23069
|
+
reply(200, { protocol: SUPERVISOR_CONTROL_PROTOCOL, result: "no-remote-session" }, false);
|
|
23070
|
+
return;
|
|
23071
|
+
}
|
|
23072
|
+
const outcome = this.access.signOutSession(readCookie(req.headers.cookie, SESSION_COOKIE));
|
|
23073
|
+
if (outcome.result === "revoked") {
|
|
23074
|
+
reply(200, { protocol: SUPERVISOR_CONTROL_PROTOCOL, result: "signed-out", id: outcome.id }, true);
|
|
23075
|
+
return;
|
|
23076
|
+
}
|
|
23077
|
+
if (outcome.result === "legacy") {
|
|
23078
|
+
reply(200, { protocol: SUPERVISOR_CONTROL_PROTOCOL, result: "cookie-cleared" }, true);
|
|
23079
|
+
return;
|
|
23080
|
+
}
|
|
23081
|
+
reply(401, { protocol: SUPERVISOR_CONTROL_PROTOCOL, result: "no-remote-session" }, true);
|
|
23082
|
+
}
|
|
22962
23083
|
/**
|
|
22963
23084
|
* Trade `?frizz_code=…` for a cookie, then bounce to the same URL
|
|
22964
23085
|
* without the secret, so it never lands in history, a Referer header, or a screenshot.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frizz",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A local web UI for running many coding agents at once",
|
|
6
6
|
"keywords": [
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"frizzServer": {
|
|
68
68
|
"package": "frizz-server",
|
|
69
|
-
"version": "0.15.
|
|
69
|
+
"version": "0.15.7",
|
|
70
70
|
"protocol": 1,
|
|
71
71
|
"dataEpoch": 1
|
|
72
72
|
}
|