ftown 0.20.9 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attention-events.d.ts +1 -1
- package/dist/attention-store.d.ts +3 -0
- package/dist/attention-store.js +25 -1
- package/dist/attention-store.js.map +1 -1
- package/dist/centrifugo-client.d.ts +28 -11
- package/dist/centrifugo-client.js +84 -70
- package/dist/centrifugo-client.js.map +1 -1
- package/dist/cli-client.d.ts +11 -1
- package/dist/cli-client.js +23 -3
- package/dist/cli-client.js.map +1 -1
- package/dist/command-rpc.d.ts +2 -0
- package/dist/command-rpc.js +59 -17
- package/dist/command-rpc.js.map +1 -1
- package/dist/identity-reset.d.ts +4 -1
- package/dist/identity-reset.js +54 -6
- package/dist/identity-reset.js.map +1 -1
- package/dist/index.js +194 -288
- package/dist/index.js.map +1 -1
- package/dist/ledger-pairing-client.d.ts +28 -0
- package/dist/ledger-pairing-client.js +154 -0
- package/dist/ledger-pairing-client.js.map +1 -0
- package/dist/ledger-relay.d.ts +159 -0
- package/dist/ledger-relay.js +595 -0
- package/dist/ledger-relay.js.map +1 -0
- package/dist/ledger-reoffer.d.ts +56 -0
- package/dist/ledger-reoffer.js +210 -0
- package/dist/ledger-reoffer.js.map +1 -0
- package/dist/ledger-startup.d.ts +176 -0
- package/dist/ledger-startup.js +233 -0
- package/dist/ledger-startup.js.map +1 -0
- package/dist/local-api-server.d.ts +3 -0
- package/dist/local-api-server.js +68 -6
- package/dist/local-api-server.js.map +1 -1
- package/dist/mail-delivery.d.ts +26 -3
- package/dist/mail-delivery.js +65 -7
- package/dist/mail-delivery.js.map +1 -1
- package/dist/preview-protocol.d.ts +19 -3
- package/dist/preview-protocol.js +14 -0
- package/dist/preview-protocol.js.map +1 -1
- package/dist/relay-token.d.ts +45 -0
- package/dist/relay-token.js +138 -0
- package/dist/relay-token.js.map +1 -0
- package/dist/revocation-gating.d.ts +0 -13
- package/dist/revocation-gating.js +0 -16
- package/dist/revocation-gating.js.map +1 -1
- package/dist/security/bridge/identity.d.ts +2 -47
- package/dist/security/bridge/identity.js +3 -3
- package/dist/security/bridge/identity.js.map +1 -1
- package/dist/security/bridge/ledger-identity.d.ts +48 -0
- package/dist/security/bridge/ledger-identity.js +342 -0
- package/dist/security/bridge/ledger-identity.js.map +1 -0
- package/dist/security/bridge/ledger-rpc.d.ts +34 -0
- package/dist/security/bridge/ledger-rpc.js +58 -0
- package/dist/security/bridge/ledger-rpc.js.map +1 -0
- package/dist/security/bridge/ledger-store.d.ts +153 -0
- package/dist/security/bridge/ledger-store.js +825 -0
- package/dist/security/bridge/ledger-store.js.map +1 -0
- package/dist/security/bridge/metadata.d.ts +2 -2
- package/dist/security/bridge/permissions.js +4 -3
- package/dist/security/bridge/permissions.js.map +1 -1
- package/dist/security/bridge/policy.d.ts +43 -23
- package/dist/security/bridge/policy.js +214 -146
- package/dist/security/bridge/policy.js.map +1 -1
- package/dist/security/bridge/security.d.ts +212 -33
- package/dist/security/bridge/security.js +437 -165
- package/dist/security/bridge/security.js.map +1 -1
- package/dist/security/crypto.js +1 -1
- package/dist/security/crypto.js.map +1 -1
- package/dist/security/ledger/announce.d.ts +32 -0
- package/dist/security/ledger/announce.js +101 -0
- package/dist/security/ledger/announce.js.map +1 -0
- package/dist/security/ledger/channels.d.ts +5 -0
- package/dist/security/ledger/channels.js +21 -0
- package/dist/security/ledger/channels.js.map +1 -0
- package/dist/security/ledger/ledger.d.ts +154 -0
- package/dist/security/ledger/ledger.js +878 -0
- package/dist/security/ledger/ledger.js.map +1 -0
- package/dist/security/ledger/pairing.d.ts +81 -0
- package/dist/security/ledger/pairing.js +414 -0
- package/dist/security/ledger/pairing.js.map +1 -0
- package/dist/security/ledger/proof.d.ts +36 -0
- package/dist/security/ledger/proof.js +193 -0
- package/dist/security/ledger/proof.js.map +1 -0
- package/dist/security/ledger/protocol.d.ts +113 -0
- package/dist/security/ledger/protocol.js +16 -0
- package/dist/security/ledger/protocol.js.map +1 -0
- package/dist/security/ledger/testing.d.ts +27 -0
- package/dist/security/ledger/testing.js +70 -0
- package/dist/security/ledger/testing.js.map +1 -0
- package/dist/security/ledger/webauthn.d.ts +12 -0
- package/dist/security/ledger/webauthn.js +147 -0
- package/dist/security/ledger/webauthn.js.map +1 -0
- package/dist/security/protocol.d.ts +5 -10
- package/dist/security-cli.d.ts +5 -0
- package/dist/security-cli.js +28 -16
- package/dist/security-cli.js.map +1 -1
- package/dist/session-controller.d.ts +9 -3
- package/dist/session-controller.js +120 -20
- package/dist/session-controller.js.map +1 -1
- package/dist/session-inbox-protocol.d.ts +2 -0
- package/dist/session-preview.d.ts +20 -5
- package/dist/session-preview.js +185 -37
- package/dist/session-preview.js.map +1 -1
- package/dist/session-store.js +19 -1
- package/dist/session-store.js.map +1 -1
- package/dist/sessions-commands.js +41 -7
- package/dist/sessions-commands.js.map +1 -1
- package/dist/token-provider.d.ts +39 -0
- package/dist/token-provider.js +101 -0
- package/dist/token-provider.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/opencode-plugin/ftown.js +15 -2
- package/package.json +2 -2
- package/skills/ftown/SKILL.md +18 -7
- package/skills/ftown/references/sessions.md +38 -12
- package/dist/bridge-auth.d.ts +0 -31
- package/dist/bridge-auth.js +0 -70
- package/dist/bridge-auth.js.map +0 -1
- package/dist/pairing-client.d.ts +0 -62
- package/dist/pairing-client.js +0 -469
- package/dist/pairing-client.js.map +0 -1
- package/dist/rotating-token-refresher.d.ts +0 -42
- package/dist/rotating-token-refresher.js +0 -91
- package/dist/rotating-token-refresher.js.map +0 -1
- package/dist/security/account-crypto.d.ts +0 -33
- package/dist/security/account-crypto.js +0 -649
- package/dist/security/account-crypto.js.map +0 -1
- package/dist/security/account-protocol.d.ts +0 -228
- package/dist/security/account-protocol.js +0 -2
- package/dist/security/account-protocol.js.map +0 -1
- package/dist/security/bridge/account-client.d.ts +0 -7
- package/dist/security/bridge/account-client.js +0 -101
- package/dist/security/bridge/account-client.js.map +0 -1
- package/dist/security/bridge/account-state.d.ts +0 -105
- package/dist/security/bridge/account-state.js +0 -79
- package/dist/security/bridge/account-state.js.map +0 -1
- package/dist/security/bridge/pending-approval.d.ts +0 -21
- package/dist/security/bridge/pending-approval.js +0 -80
- package/dist/security/bridge/pending-approval.js.map +0 -1
- package/dist/security/bridge/pending-enrollment.d.ts +0 -33
- package/dist/security/bridge/pending-enrollment.js +0 -177
- package/dist/security/bridge/pending-enrollment.js.map +0 -1
- package/dist/security/bridge/revocation.d.ts +0 -14
- package/dist/security/bridge/revocation.js +0 -40
- package/dist/security/bridge/revocation.js.map +0 -1
- package/dist/security/pending-approval-crypto.d.ts +0 -29
- package/dist/security/pending-approval-crypto.js +0 -716
- package/dist/security/pending-approval-crypto.js.map +0 -1
- package/dist/security/pending-approval-protocol.d.ts +0 -329
- package/dist/security/pending-approval-protocol.js +0 -2
- package/dist/security/pending-approval-protocol.js.map +0 -1
- package/dist/security/security-nudge.d.ts +0 -31
- package/dist/security/security-nudge.js +0 -98
- package/dist/security/security-nudge.js.map +0 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Account-mode startup decisions (passkey ledger), kept out of index.ts so each one is unit-tested:
|
|
3
|
+
* - step 2: which identity reset runs before anything else (resetIdentityForStart);
|
|
4
|
+
* - steps 3-4: pairing, ledger recovery and this bridge's own status (startAccountIdentity);
|
|
5
|
+
* - steps 5-6: the first relay token (online, offline or exit) and the endpoint registration (startRelay, createEndpointRegistrar).
|
|
6
|
+
* Solo and --local never enter any of it: they only finish a reset that is already pending, exactly as before.
|
|
7
|
+
*/
|
|
8
|
+
import { beginIdentityReset, completePendingIdentityReset } from './identity-reset.js';
|
|
9
|
+
import { hasLegacyAccountState, loadOrCreateLedgerIdentity, readLedgerIdentity } from './security/bridge/ledger-identity.js';
|
|
10
|
+
import { BridgeLedgerStore, LedgerStoreError, reconcileIdentityAndStore } from './security/bridge/ledger-store.js';
|
|
11
|
+
import { MISSING_RELAY_URL_MESSAGE, isRefusal, relayTokenFailureMessage } from './ledger-relay.js';
|
|
12
|
+
export const LEGACY_ACCOUNT_RESET_MESSAGE = '[Bridge] Old account data from a previous ftown version was cleared. Pair this computer again with the link below.';
|
|
13
|
+
export const NOT_IN_LEDGER_MESSAGE = "This computer is not in your account's ledger. Run `ftown start --new-identity` to pair it again.";
|
|
14
|
+
export const PAIRING_REJECTED_HINT = 'Pairing did not complete. Run `ftown start` to try again.';
|
|
15
|
+
export const PAIRING_REVOKED_MESSAGE = "[Bridge] This computer's id was removed from the account before pairing completed. "
|
|
16
|
+
+ 'Run `ftown start --new-identity` to pair it with a new id.';
|
|
17
|
+
export const SERVER_LEDGER_MISMATCH_MESSAGE = "[Bridge] The server's account ledger does not list this computer. If it was removed from your account, run `ftown start --new-identity` to pair it again.";
|
|
18
|
+
export const UPDATE_RESTART_UNPAIRED_MESSAGE = '[Bridge] An update restart cannot pair this computer. Run `ftown start` to pair it.';
|
|
19
|
+
export const UPDATED_TO_LEDGER_MESSAGE = '[Bridge] ftown was updated to the passkey ledger; run `ftown start` to pair this computer.';
|
|
20
|
+
export const DO_NOT_RESET_LINE = '[Bridge] Do NOT run --new-identity: this computer may hold the only copy. Open Security in the dashboard to choose a history.';
|
|
21
|
+
export const HELD_OFFLINE_LINE = '[Bridge] Starting offline: local sessions keep working, and the relay is retried in the background.';
|
|
22
|
+
export const HELD_NO_RELAY_URL_LINE = '[Bridge] No relay URL was saved by an earlier start, so this computer cannot start offline. Run `ftown start` again later.';
|
|
23
|
+
const plural = (count, word) => `${count} ${word}${count === 1 ? '' : 's'}`;
|
|
24
|
+
/** Printed once when the server refuses the key of a computer that is an active member of its own history (step 5): never reset advice. */
|
|
25
|
+
export function heldHistoryNotice(unique) {
|
|
26
|
+
const detail = unique === null || unique.records === 0
|
|
27
|
+
? 'this computer holds changes the server does not have'
|
|
28
|
+
: `this computer holds account history the server does not have: ${plural(unique.removals, 'removal')} and ${plural(unique.passkeys, 'passkey')} `
|
|
29
|
+
+ `among ${plural(unique.records, 'change')} after revision ${unique.afterRevision}`;
|
|
30
|
+
return [`[Bridge] The ftown server refused this computer's key, and ${detail}.`, DO_NOT_RESET_LINE];
|
|
31
|
+
}
|
|
32
|
+
/** Only account bridges use the passkey ledger (legacy reset, pairing, ledger replica, relay token by proof). */
|
|
33
|
+
export function usesLedger(mode) {
|
|
34
|
+
return mode === 'account';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Step 2. Account mode: `--new-identity`, or any v1 account state on disk, starts a manual reset (the latter says so in one line).
|
|
38
|
+
* An update restart (a v1 -> v2 auto-update) has no visible terminal for the pairing link: with v1 state on disk it changes nothing
|
|
39
|
+
* and exits, and the next `ftown start` resets and pairs. Every mode then finishes a pending reset, as before (a revoked restart or a
|
|
40
|
+
* crash may have left one).
|
|
41
|
+
*/
|
|
42
|
+
export function resetIdentityForStart(input, deps = {}) {
|
|
43
|
+
const legacy = deps.hasLegacyAccountState ?? hasLegacyAccountState;
|
|
44
|
+
const begin = deps.beginIdentityReset ?? beginIdentityReset;
|
|
45
|
+
const complete = deps.completePendingIdentityReset ?? completePendingIdentityReset;
|
|
46
|
+
if (usesLedger(input.mode)) {
|
|
47
|
+
if (input.newIdentity)
|
|
48
|
+
begin(input.dataDir, 'manual');
|
|
49
|
+
else if (legacy(input.dataDir)) {
|
|
50
|
+
if (input.updateRestart)
|
|
51
|
+
return { kind: 'exit', message: UPDATED_TO_LEDGER_MESSAGE };
|
|
52
|
+
input.log(LEGACY_ACCOUNT_RESET_MESSAGE);
|
|
53
|
+
begin(input.dataDir, 'manual');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { kind: 'continue', reset: complete(input.dataDir) };
|
|
57
|
+
}
|
|
58
|
+
/** Step 4. Only a signed revocation resets the identity; a bridge the ledger does not list fails closed and is never reset. */
|
|
59
|
+
export function ownStatusAction(own) {
|
|
60
|
+
if (own === 'active')
|
|
61
|
+
return 'continue';
|
|
62
|
+
if (own === 'revoked')
|
|
63
|
+
return 'revoked_restart';
|
|
64
|
+
return 'exit_absent';
|
|
65
|
+
}
|
|
66
|
+
const messageOf = (error) => error instanceof Error ? error.message : String(error);
|
|
67
|
+
export function ledgerUnavailableMessage(reason) {
|
|
68
|
+
return `[Bridge] Could not load your account ledger from the server (${reason}). Check the connection and run \`ftown start\` again.`;
|
|
69
|
+
}
|
|
70
|
+
function bound(identity) {
|
|
71
|
+
return identity !== null && identity.endpoint !== null && identity.accountId !== null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Steps 3-4, account mode only, before any network use of the identity:
|
|
75
|
+
* - keys exist (created when absent), then identity and replica are reconciled;
|
|
76
|
+
* - 'unpaired' -> one pairing attempt (expired/rejected -> exit 1 with the hint);
|
|
77
|
+
* - 'needs_ledger' -> the server ledger is adopted only through adoptBound (any failure -> exit 1; the identity stays bound);
|
|
78
|
+
* - own status from the trusted replica: 'revoked' -> revoked restart; 'absent' -> exit 1 (never a reset); 'active' -> ready.
|
|
79
|
+
* Corrupted local files throw (they are never reset).
|
|
80
|
+
*/
|
|
81
|
+
export async function startAccountIdentity(deps) {
|
|
82
|
+
const loadOrCreate = deps.loadOrCreateIdentity ?? loadOrCreateLedgerIdentity;
|
|
83
|
+
const reconcile = deps.reconcile ?? ((dataDir) => reconcileIdentityAndStore(dataDir));
|
|
84
|
+
const readIdentity = deps.readIdentity ?? readLedgerIdentity;
|
|
85
|
+
const keys = await loadOrCreate(deps.dataDir);
|
|
86
|
+
const reconciled = await reconcile(deps.dataDir);
|
|
87
|
+
if (reconciled === 'unpaired') {
|
|
88
|
+
if (!deps.allowPairing)
|
|
89
|
+
return { kind: 'exit', message: UPDATE_RESTART_UNPAIRED_MESSAGE };
|
|
90
|
+
try {
|
|
91
|
+
await deps.pair(keys, new BridgeLedgerStore(deps.dataDir, { bridgeId: deps.bridgeId }));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const code = messageOf(error);
|
|
95
|
+
if (code === 'pairing_expired')
|
|
96
|
+
return { kind: 'exit', message: null };
|
|
97
|
+
if (code === 'pairing_rejected')
|
|
98
|
+
return { kind: 'exit', message: PAIRING_REJECTED_HINT };
|
|
99
|
+
// Not a removal of a paired bridge: nothing is reset; the owner picks a new id.
|
|
100
|
+
if (code === 'pairing_revoked')
|
|
101
|
+
return { kind: 'exit', message: PAIRING_REVOKED_MESSAGE };
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const identity = await readIdentity(deps.dataDir);
|
|
106
|
+
if (!bound(identity))
|
|
107
|
+
return { kind: 'exit', message: PAIRING_REJECTED_HINT };
|
|
108
|
+
const store = new BridgeLedgerStore(deps.dataDir, { accountId: identity.accountId, bridgeId: deps.bridgeId });
|
|
109
|
+
if (reconciled === 'needs_ledger') {
|
|
110
|
+
try {
|
|
111
|
+
await store.adoptBound(await deps.fetchLedger(identity.accountId));
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof LedgerStoreError && error.code === 'ledger_store_bound_rejected')
|
|
115
|
+
return { kind: 'exit', message: SERVER_LEDGER_MISMATCH_MESSAGE };
|
|
116
|
+
return { kind: 'exit', message: ledgerUnavailableMessage(messageOf(error)) };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const action = ownStatusAction(await store.ownBridgeStatus({
|
|
120
|
+
bridgeId: deps.bridgeId, endpointId: identity.endpoint.id, endpointPublicKey: identity.endpoint.publicKey, authPublicKey: identity.authPublicKey,
|
|
121
|
+
}));
|
|
122
|
+
if (action === 'revoked_restart')
|
|
123
|
+
return { kind: 'revoked' };
|
|
124
|
+
if (action === 'exit_absent')
|
|
125
|
+
return { kind: 'exit', message: NOT_IN_LEDGER_MESSAGE };
|
|
126
|
+
return { kind: 'ready', identity, store, reconciled };
|
|
127
|
+
}
|
|
128
|
+
export function offlineWithoutRelayUrlMessage(reason) {
|
|
129
|
+
return `[Bridge] Could not reach the ftown server (${reason}) and no relay URL was saved by an earlier start. Check the connection and run \`ftown start\` again.`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Step 5, the first relay token. A proven revocation (the token hooks offer the server's evidence first) wins over everything. A refusal
|
|
133
|
+
* (401/403) of a computer that is still an active member of its own history (`held` returns the notice: a divergence with the server, a
|
|
134
|
+
* server behind us, a failed re-offer, or any refusal without proof of our removal) starts OFFLINE and prints the notice once, never the
|
|
135
|
+
* reset advice: this computer may hold the only copy. Any other refusal (this computer is not an active member: unpaired/absent) exits
|
|
136
|
+
* with the reset advice. A temporary failure (network, timeout, 5xx, cooldown) starts offline with the saved relay URL. Without a saved
|
|
137
|
+
* relay URL there is no offline start: exit. After a successful token the revocation is checked again (the token response's ledger may
|
|
138
|
+
* prove it).
|
|
139
|
+
*/
|
|
140
|
+
export async function startRelay(input) {
|
|
141
|
+
let token;
|
|
142
|
+
try {
|
|
143
|
+
token = await input.getToken();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (input.bridgeRevoked())
|
|
147
|
+
return { kind: 'revoked' };
|
|
148
|
+
if (isRefusal(error)) {
|
|
149
|
+
const notice = input.held?.() ?? null;
|
|
150
|
+
if (notice === null)
|
|
151
|
+
return { kind: 'exit', message: relayTokenFailureMessage(error, input.apiUrl) };
|
|
152
|
+
const savedUrl = input.relayUrl();
|
|
153
|
+
if (savedUrl === null)
|
|
154
|
+
return { kind: 'exit', message: [...notice, HELD_NO_RELAY_URL_LINE].join('\n') };
|
|
155
|
+
return { kind: 'offline', relayUrl: savedUrl, reason: messageOf(error), notice: [...notice, HELD_OFFLINE_LINE] };
|
|
156
|
+
}
|
|
157
|
+
const reason = messageOf(error);
|
|
158
|
+
const relayUrl = input.relayUrl();
|
|
159
|
+
return relayUrl === null ? { kind: 'exit', message: offlineWithoutRelayUrlMessage(reason) } : { kind: 'offline', relayUrl, reason };
|
|
160
|
+
}
|
|
161
|
+
if (input.bridgeRevoked())
|
|
162
|
+
return { kind: 'revoked' };
|
|
163
|
+
const relayUrl = input.relayUrl();
|
|
164
|
+
if (relayUrl === null)
|
|
165
|
+
return { kind: 'exit', message: MISSING_RELAY_URL_MESSAGE };
|
|
166
|
+
return { kind: 'online', token, relayUrl };
|
|
167
|
+
}
|
|
168
|
+
export function endpointRefusedMessage(reason) {
|
|
169
|
+
return `[Bridge] The ftown server refused this computer's encrypted endpoint (${reason}). If it was removed from your account, run \`ftown start --new-identity\` to pair it again.`;
|
|
170
|
+
}
|
|
171
|
+
export function createEndpointRegistrar(deps) {
|
|
172
|
+
const schedule = deps.schedule ?? ((run, ms) => { const timer = setTimeout(run, ms); timer.unref(); return () => clearTimeout(timer); });
|
|
173
|
+
const maxDelay = deps.maxDelayMs ?? 600_000;
|
|
174
|
+
let delay = deps.baseDelayMs ?? 15_000;
|
|
175
|
+
let done = false;
|
|
176
|
+
let stopped = false;
|
|
177
|
+
let cancel = null;
|
|
178
|
+
const next = () => {
|
|
179
|
+
const wait = Math.max(deps.tokens.cooldownRemainingMs(), delay);
|
|
180
|
+
delay = Math.min(delay * 2, maxDelay);
|
|
181
|
+
cancel = schedule(() => { cancel = null; void attempt(); }, wait);
|
|
182
|
+
};
|
|
183
|
+
const attempt = async () => {
|
|
184
|
+
if (done || stopped)
|
|
185
|
+
return;
|
|
186
|
+
try {
|
|
187
|
+
await deps.register(await deps.tokens.getToken());
|
|
188
|
+
done = true;
|
|
189
|
+
deps.log('[Bridge] Registered this computer\'s encrypted endpoint.');
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
if (stopped)
|
|
193
|
+
return;
|
|
194
|
+
if (isRefusal(error) && !deps.held?.()) {
|
|
195
|
+
// A verified revocation restarts the bridge on its own; anything else needs the owner.
|
|
196
|
+
if (!deps.bridgeRevoked())
|
|
197
|
+
deps.log(endpointRefusedMessage(messageOf(error)));
|
|
198
|
+
stopped = true;
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
next();
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
async registerNow(token) {
|
|
206
|
+
try {
|
|
207
|
+
await deps.register(token);
|
|
208
|
+
done = true;
|
|
209
|
+
return { kind: 'registered' };
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (deps.bridgeRevoked())
|
|
213
|
+
return { kind: 'revoked' };
|
|
214
|
+
if (isRefusal(error) && !deps.held?.())
|
|
215
|
+
return { kind: 'exit', message: endpointRefusedMessage(messageOf(error)) };
|
|
216
|
+
deps.log(`[Bridge] Could not register this computer's encrypted endpoint yet (${messageOf(error)}); retrying in the background.`);
|
|
217
|
+
return { kind: 'retry' };
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
retryInBackground() {
|
|
221
|
+
if (done || stopped || cancel !== null)
|
|
222
|
+
return;
|
|
223
|
+
next();
|
|
224
|
+
},
|
|
225
|
+
registered: () => done,
|
|
226
|
+
stop() {
|
|
227
|
+
stopped = true;
|
|
228
|
+
cancel?.();
|
|
229
|
+
cancel = null;
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=ledger-startup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-startup.js","sourceRoot":"","sources":["../src/ledger-startup.ts"],"names":[],"mappings":"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"}
|
|
@@ -73,6 +73,9 @@ export declare class LocalApiServer extends EventEmitter<HookServerEvents> {
|
|
|
73
73
|
private localEventBytes;
|
|
74
74
|
private readonly localEventWaiters;
|
|
75
75
|
setSecurity(security: BridgeSecurityService, info: BridgeSecurityInfo, command: (message: VerifiedApplication) => Promise<ApplicationJson>, observer?: SecureExecutionObserver): void;
|
|
76
|
+
/** GET /api/security/info: the static info, plus in account mode `ledger: { divergence, serverBehind, removedOnOtherHistory }` (CONTRACT §5: a computer
|
|
77
|
+
* holding a different account history is shown with a warning). */
|
|
78
|
+
private securityInfoPayload;
|
|
76
79
|
recordPublication(channel: string, data: unknown): void;
|
|
77
80
|
dispatchSecure(message: VerifiedApplication, clientGone?: AbortSignal): Promise<ApplicationJson>;
|
|
78
81
|
private port;
|
package/dist/local-api-server.js
CHANGED
|
@@ -137,6 +137,15 @@ export class LocalApiServer extends EventEmitter {
|
|
|
137
137
|
this.secureCommand = command;
|
|
138
138
|
this.secureObserver = observer;
|
|
139
139
|
}
|
|
140
|
+
/** GET /api/security/info: the static info, plus in account mode `ledger: { divergence, serverBehind, removedOnOtherHistory }` (CONTRACT §5: a computer
|
|
141
|
+
* holding a different account history is shown with a warning). */
|
|
142
|
+
securityInfoPayload() {
|
|
143
|
+
const info = this.securityInfo;
|
|
144
|
+
if (!this.security?.options.ledger)
|
|
145
|
+
return info;
|
|
146
|
+
const status = this.security.ledgerStatus();
|
|
147
|
+
return { ...info, ledger: { divergence: status.divergence, serverBehind: status.serverBehind, removedOnOtherHistory: status.removedOnOtherHistory } };
|
|
148
|
+
}
|
|
140
149
|
recordPublication(channel, data) {
|
|
141
150
|
const item = { channel, data: applicationJson(data), cursor: ++this.localEventSequence };
|
|
142
151
|
this.localEvents.push(item);
|
|
@@ -459,7 +468,7 @@ export class LocalApiServer extends EventEmitter {
|
|
|
459
468
|
return;
|
|
460
469
|
}
|
|
461
470
|
if (path === '/api/security/info' && req.method === 'GET') {
|
|
462
|
-
jsonResponse(res, 200, this.
|
|
471
|
+
jsonResponse(res, 200, this.securityInfoPayload());
|
|
463
472
|
return;
|
|
464
473
|
}
|
|
465
474
|
if (req.method === 'POST' && (path === '/api/security/envelope' || path === '/api/security/events')) {
|
|
@@ -830,11 +839,42 @@ export class LocalApiServer extends EventEmitter {
|
|
|
830
839
|
return;
|
|
831
840
|
}
|
|
832
841
|
const body = await parseBody(req);
|
|
833
|
-
if (req.method === 'PUT'
|
|
842
|
+
if (req.method === 'PUT') {
|
|
843
|
+
if (Object.keys(body).some((key) => key !== 'paths' && key !== 'mode') || !('paths' in body) || !Array.isArray(body.paths) || body.paths.length < 1 || body.paths.length > 12 || body.paths.some((entry) => typeof entry !== 'string' || entry.length === 0)) {
|
|
844
|
+
jsonResponse(res, 400, { error: 'Invalid preview request' });
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
const mode = body.mode;
|
|
848
|
+
if (mode !== undefined && mode !== 'replace' && mode !== 'add') {
|
|
849
|
+
jsonResponse(res, 400, { error: 'Invalid preview request' });
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const result = mode === 'add' ? await controller.addPreview(sessionPreviewMatch[1], body.paths) : await controller.setPreview(sessionPreviewMatch[1], body.paths);
|
|
853
|
+
if (!result.ok) {
|
|
854
|
+
jsonResponse(res, result.code === 'not_found' ? 404 : result.code === 'too_large' ? 413 : 400, { error: result.message });
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
jsonResponse(res, 200, { session: toWireSession(result.session) });
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (Object.keys(body).some((key) => key !== 'names')) {
|
|
861
|
+
jsonResponse(res, 400, { error: 'Invalid preview request' });
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (body.names === undefined || (Array.isArray(body.names) && body.names.length === 0)) {
|
|
865
|
+
const result = await controller.clearPreview(sessionPreviewMatch[1]);
|
|
866
|
+
if (!result.ok) {
|
|
867
|
+
jsonResponse(res, result.code === 'not_found' ? 404 : 400, { error: result.message });
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
jsonResponse(res, 200, { session: toWireSession(result.session) });
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (!Array.isArray(body.names) || body.names.some((entry) => typeof entry !== 'string' || entry.length === 0)) {
|
|
834
874
|
jsonResponse(res, 400, { error: 'Invalid preview request' });
|
|
835
875
|
return;
|
|
836
876
|
}
|
|
837
|
-
const result =
|
|
877
|
+
const result = await controller.removePreview(sessionPreviewMatch[1], body.names);
|
|
838
878
|
if (!result.ok) {
|
|
839
879
|
jsonResponse(res, result.code === 'not_found' ? 404 : result.code === 'too_large' ? 413 : 400, { error: result.message });
|
|
840
880
|
return;
|
|
@@ -895,6 +935,7 @@ export class LocalApiServer extends EventEmitter {
|
|
|
895
935
|
return;
|
|
896
936
|
}
|
|
897
937
|
await controller.remove(sessionId);
|
|
938
|
+
this.mail.markSessionEnded(sessionId);
|
|
898
939
|
jsonResponse(res, 200, { removed: true, sessionId });
|
|
899
940
|
return;
|
|
900
941
|
}
|
|
@@ -1357,6 +1398,18 @@ export class LocalApiServer extends EventEmitter {
|
|
|
1357
1398
|
}
|
|
1358
1399
|
return;
|
|
1359
1400
|
}
|
|
1401
|
+
if (payload.hook_event_name === 'FtownWakeSubmitted') {
|
|
1402
|
+
// Opencode announces the wake before submitting it (same 200 either way).
|
|
1403
|
+
// Record only: never forwarded to the dashboard, never attention.
|
|
1404
|
+
const submittedId = typeof payload.ftown_session_id === 'string' ? payload.ftown_session_id : undefined;
|
|
1405
|
+
const submittedSource = typeof payload.ftown_session_source === 'string' ? payload.ftown_session_source : undefined;
|
|
1406
|
+
if (submittedId && submittedSource !== 'workspace' && /^[a-zA-Z0-9_-]+$/.test(submittedId)
|
|
1407
|
+
&& await this.store?.loadSession(submittedId)) {
|
|
1408
|
+
this.mail.notePluginHandoff(submittedId);
|
|
1409
|
+
}
|
|
1410
|
+
jsonResponse(res, 200, { ok: true });
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1360
1413
|
const hookEventName = typeof payload.hook_event_name === 'string' ? payload.hook_event_name : undefined;
|
|
1361
1414
|
let ftownSessionId = typeof payload.ftown_session_id === 'string' ? payload.ftown_session_id : undefined;
|
|
1362
1415
|
let source = typeof payload.ftown_session_source === 'string' ? payload.ftown_session_source : undefined;
|
|
@@ -1369,22 +1422,31 @@ export class LocalApiServer extends EventEmitter {
|
|
|
1369
1422
|
jsonResponse(res, 200, { ok: true });
|
|
1370
1423
|
return;
|
|
1371
1424
|
}
|
|
1372
|
-
const { ftown_session_id: _, ftown_session_source: __, attentionId: _attentionId, attentionSequence: _attentionSequence, attentionCreatedAt: _attentionCreatedAt, ...
|
|
1425
|
+
const { ftown_session_id: _, ftown_session_source: __, attentionId: _attentionId, attentionSequence: _attentionSequence, attentionCreatedAt: _attentionCreatedAt, ...restWithInjected } = payload;
|
|
1426
|
+
// Never trust a harness-supplied injected flag.
|
|
1427
|
+
const { injected: _incomingInjected, ...rest } = restWithInjected;
|
|
1428
|
+
const isPromptSubmit = hookEventName === 'UserPromptSubmit' || hookEventName === 'beforeSubmitPrompt';
|
|
1429
|
+
const injected = isPromptSubmit && this.mail.isInjectedPrompt(ftownSessionId, rest);
|
|
1373
1430
|
let committedAttention = undefined;
|
|
1374
1431
|
// Workspace fallback can identify a foreign agent in the same directory.
|
|
1375
1432
|
// Only known explicit/native-attributed sessions may produce authoritative records.
|
|
1376
1433
|
if (this.attention && source !== 'workspace' && /^[a-zA-Z0-9_-]+$/.test(ftownSessionId) && await this.store?.loadSession(ftownSessionId)) {
|
|
1377
|
-
committedAttention = await this.attention.record(ftownSessionId, hookEventName, rest);
|
|
1434
|
+
committedAttention = await this.attention.record(ftownSessionId, hookEventName, rest, isPromptSubmit ? { promptOrigin: injected ? 'injected' : 'human' } : undefined);
|
|
1378
1435
|
}
|
|
1379
1436
|
if (hookEventName === 'UserPromptSubmit' || hookEventName === 'PreToolUse' || hookEventName === 'PostToolUse')
|
|
1380
1437
|
this.mail.markAgentBusy(ftownSessionId);
|
|
1381
|
-
else if (hookEventName === 'Stop'
|
|
1438
|
+
else if (hookEventName === 'Stop')
|
|
1382
1439
|
this.mail.markAgentIdle(ftownSessionId);
|
|
1440
|
+
else if (hookEventName === 'SessionEnd') {
|
|
1441
|
+
this.mail.markAgentIdle(ftownSessionId);
|
|
1442
|
+
this.mail.markSessionEnded(ftownSessionId);
|
|
1443
|
+
}
|
|
1383
1444
|
const conversationId = payload.conversation_id;
|
|
1384
1445
|
if (typeof conversationId === 'string' && conversationId && source !== 'workspace')
|
|
1385
1446
|
registerSessionConversation(ftownSessionId, conversationId);
|
|
1386
1447
|
const hookEvent = { sessionId: ftownSessionId, eventName: hookEventName, data: {
|
|
1387
1448
|
...rest,
|
|
1449
|
+
...(injected ? { injected: true } : {}),
|
|
1388
1450
|
...(committedAttention ? { attentionId: committedAttention.id, attentionSequence: committedAttention.sequence, attentionCreatedAt: committedAttention.createdAt } : {}),
|
|
1389
1451
|
}, source };
|
|
1390
1452
|
// Persistence is complete before listeners can publish and before HTTP success.
|