@awak-app/simy-cli 0.7.19 → 0.7.20
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 +10 -6
- package/package.json +1 -1
- package/src/auto-update.js +17 -1
- package/src/cli-contract.js +1 -1
- package/src/desktop-access.js +1 -1
- package/src/desktop-host.js +21 -6
- package/src/desktop-update-settings.js +15 -0
- package/src/update-detachment/runtime.js +8 -4
package/README.md
CHANGED
|
@@ -248,15 +248,19 @@ Closing the window keeps the agent running. Open the app again or choose
|
|
|
248
248
|
**Show SIMY** from its menu bar icon to return. **Quit SIMY** asks before
|
|
249
249
|
interrupting active tasks. The normal `simy` command remains available.
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
The macOS **Automatic updates** switch saves this Mac's choice across restarts.
|
|
252
|
+
Compatible signed installations enable it by default. Patch/minor updates wait
|
|
253
|
+
for idle and verify the new runtime before handoff; failed startup restores the
|
|
254
|
+
previous installed version. The app then switches to the verified new interface
|
|
255
|
+
when no unfinished work or confirmation is pending. Turning the switch off keeps
|
|
256
|
+
manual checks and **Download DMG** available. An explicit `SIMY_AUTO_UPDATE=0`
|
|
257
|
+
remains authoritative, and major updates continue to use the DMG.
|
|
258
|
+
**Stop task…** in a task
|
|
255
259
|
detail asks for confirmation and terminates its local Provider process, keeping
|
|
256
260
|
existing output and history. An explicit update can likewise stop active work
|
|
257
261
|
after confirmation; automatic updates still wait for idle. **⌘Q** exits SIMY and
|
|
258
|
-
its background services directly, including when work is running. The window
|
|
259
|
-
|
|
262
|
+
its background services directly, including when work is running. The window shows
|
|
263
|
+
the app version, and normal app launches select the newer installed interface.
|
|
260
264
|
|
|
261
265
|
Older installers that predate native app relaunch support need a one-time
|
|
262
266
|
reinstallation from the official download page to update their Applications
|
package/package.json
CHANGED
package/src/auto-update.js
CHANGED
|
@@ -86,6 +86,7 @@ export class CliAutoUpdater extends EventEmitter {
|
|
|
86
86
|
install_mode: options.installMode,
|
|
87
87
|
automatic: Boolean(
|
|
88
88
|
!options.disabled &&
|
|
89
|
+
options.autoUpdatesEnabled !== false &&
|
|
89
90
|
options.daemon &&
|
|
90
91
|
(options.installMode === "global_npm" ||
|
|
91
92
|
(options.installMode === MACOS_INSTALLER_MODE && options.installerMarker?.teamId)) &&
|
|
@@ -101,7 +102,20 @@ export class CliAutoUpdater extends EventEmitter {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
snapshot() {
|
|
104
|
-
|
|
105
|
+
const supported = !this.#options.disabled && this.#options.daemon && this.#options.requestedPort === 0 &&
|
|
106
|
+
(this.#options.installMode === 'global_npm' || this.#options.installMode === MACOS_INSTALLER_MODE && Boolean(this.#options.installerMarker?.teamId));
|
|
107
|
+
return structuredClone({ ...this.#snapshot, auto_update_supported: Boolean(supported),
|
|
108
|
+
auto_update_enabled: this.#snapshot.automatic, auto_update_busy: Boolean(this.#applying || this.#confirmedUpdate), requires_task_stop: !this.#isIdle() });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
setAutomaticUpdates(enabled) {
|
|
112
|
+
if (typeof enabled !== 'boolean') throw new Error('Choose whether automatic updates are enabled.');
|
|
113
|
+
if (enabled && !this.snapshot().auto_update_supported) throw new Error('Automatic updates are unavailable for this installation.');
|
|
114
|
+
if (this.#applying || this.#confirmedUpdate) throw new Error('Wait for the current update to finish.');
|
|
115
|
+
this.#options.autoUpdatesEnabled = enabled;
|
|
116
|
+
if (!enabled) this.#clearIdleTimer();
|
|
117
|
+
this.#setState(!enabled && this.#snapshot.state === 'waiting_for_idle' ? 'update_available' : this.#snapshot.state,
|
|
118
|
+
{ automatic: enabled, error: null });
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
// Close admission before cancellation begins. The same transaction owns the
|
|
@@ -435,6 +449,7 @@ export async function createCliAutoUpdater({
|
|
|
435
449
|
interactive,
|
|
436
450
|
requestedPort = 0,
|
|
437
451
|
disabled = false,
|
|
452
|
+
autoUpdatesEnabled = true,
|
|
438
453
|
packageRoot = PACKAGE_ROOT,
|
|
439
454
|
webOrigin,
|
|
440
455
|
dependencies = {},
|
|
@@ -458,6 +473,7 @@ export async function createCliAutoUpdater({
|
|
|
458
473
|
interactive: Boolean(interactive),
|
|
459
474
|
requestedPort,
|
|
460
475
|
disabled,
|
|
476
|
+
autoUpdatesEnabled,
|
|
461
477
|
currentVersion: packageInfo.version,
|
|
462
478
|
installMode,
|
|
463
479
|
installerMarker,
|
package/src/cli-contract.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./durable-local-task-steps-contract.js";
|
|
15
15
|
import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
|
|
16
16
|
|
|
17
|
-
export const CLI_VERSION = "0.7.
|
|
17
|
+
export const CLI_VERSION = "0.7.20";
|
|
18
18
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
19
19
|
|
|
20
20
|
export function narrativeImageCapabilityMetadata() {
|
package/src/desktop-access.js
CHANGED
|
@@ -8,7 +8,7 @@ export function validDesktopAccessPath(path, slug) {
|
|
|
8
8
|
}
|
|
9
9
|
export const desktopAccessReady = value => value?.state === 'ready';
|
|
10
10
|
const recoveryActions = new Set(['refresh', 'reconnect', 'login', 'authorization_open', 'organization_select', 'organization_cancel', 'organizations_refresh',
|
|
11
|
-
'check_update', 'update', 'prepare_update_restart', 'quit', 'task_stop', 'delivery_stop', 'delivery_pause', 'delivery_reject',
|
|
11
|
+
'check_update', 'auto_update_set_enabled', 'update', 'prepare_update_restart', 'quit', 'task_stop', 'delivery_stop', 'delivery_pause', 'delivery_reject',
|
|
12
12
|
'provider_disconnect', 'desktop_access_refresh', 'desktop_access_settings', 'desktop_access_members']);
|
|
13
13
|
export function desktopActionAllowed(access, command) {
|
|
14
14
|
return desktopAccessReady(access) || recoveryActions.has(command?.action) ||
|
package/src/desktop-host.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRemoteHostManager } from './session-supervision/remote-hosts.js';
|
|
2
|
+
import { readDesktopAutoUpdate, writeDesktopAutoUpdate } from './desktop-update-settings.js';
|
|
2
3
|
import { createUpdateRuntime } from './update-detachment/runtime.js';
|
|
3
4
|
import { createCoordinatorReconciler } from "./update-detachment/reconciliation-adapter.js";
|
|
4
5
|
import { createLedgerCoordinatorAdapters } from "./update-detachment/ledger-adapter.js";
|
|
@@ -169,6 +170,11 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
169
170
|
let encodedStatus = null, lastStatus = null, updateProgress = null;
|
|
170
171
|
let remoteHosts;
|
|
171
172
|
const emitStatus = status => {
|
|
173
|
+
const updateSettings = updater?.snapshot?.() || {};
|
|
174
|
+
status = { ...status, update: { ...status.update,
|
|
175
|
+
auto_update_supported: updateSettings.auto_update_supported === true,
|
|
176
|
+
auto_update_enabled: updateSettings.auto_update_enabled === true,
|
|
177
|
+
auto_update_busy: updateSettings.auto_update_busy === true } };
|
|
172
178
|
if (env.SIMY_REMOTE_CHILD === "1") status = { ...status, remoteSessionProtocol: 1 };
|
|
173
179
|
if (remoteHosts) {
|
|
174
180
|
void remoteHosts.update(providerLinks.codex.connected ? status : { ...status, access: { state: "unavailable" } }).catch(() => {});
|
|
@@ -644,8 +650,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
644
650
|
const sampledAccess = structuredClone(status.access || { state: 'unavailable', organizationId: null, organizationName: null });
|
|
645
651
|
if (!await acceptStatusAccess(sampledAccess)) { publishAgain = true; return; }
|
|
646
652
|
status.access = sampledAccess;
|
|
647
|
-
status.updateDetachment = updateRuntime.snapshot(coordinator.snapshot(),updater?.snapshot?.());
|
|
648
|
-
if(!desktopAccessReady(sampledAccess))status.updateDetachment={...status.updateDetachment,tasks:[],actions:status.updateDetachment.actions.filter(action=>action==='cancel_detach_update')};
|
|
653
|
+
status.updateDetachment = updateRuntime.snapshot(coordinator.snapshot(),updater?.snapshot?.(),{accessReady:desktopAccessReady(sampledAccess)});
|
|
649
654
|
const requestedLinks = await decorateProviderSettings(status);
|
|
650
655
|
const sameScope = desktopAccessReady(currentAccess) &&
|
|
651
656
|
accessScope(lastStatus?.access) === accessScope(currentAccess);
|
|
@@ -694,8 +699,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
694
699
|
const sampledAccess = structuredClone(status.access || { state: 'unavailable', organizationId: null, organizationName: null });
|
|
695
700
|
if (!await acceptStatusAccess(sampledAccess)) { publishAgain = true; return; }
|
|
696
701
|
status.access = sampledAccess;
|
|
697
|
-
status.updateDetachment = updateRuntime.snapshot(coordinator.snapshot(),updater?.snapshot?.());
|
|
698
|
-
if(!desktopAccessReady(sampledAccess))status.updateDetachment={...status.updateDetachment,tasks:[],actions:status.updateDetachment.actions.filter(action=>action==='cancel_detach_update')};
|
|
702
|
+
status.updateDetachment = updateRuntime.snapshot(coordinator.snapshot(),updater?.snapshot?.(),{accessReady:desktopAccessReady(sampledAccess)});
|
|
699
703
|
const requestedScope = accessScope(currentAccess);
|
|
700
704
|
const requestedLinks = await decorateProviderSettings(status);
|
|
701
705
|
const allRecords = await recordReader.list();
|
|
@@ -841,6 +845,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
841
845
|
updater = await (dependencies.createUpdater || (process.platform === "win32" ? (() => createWindowsDesktopUpdater({ version: packageInfo.version, disabled: env.SIMY_AUTO_UPDATE === "0" })) : createCliAutoUpdater))({
|
|
842
846
|
daemon: true, interactive: true, webOrigin,
|
|
843
847
|
disabled: env.SIMY_AUTO_UPDATE === "0",
|
|
848
|
+
autoUpdatesEnabled: readDesktopAutoUpdate(root),
|
|
844
849
|
...(updatePackageRoot ? { packageRoot: updatePackageRoot } : {}),
|
|
845
850
|
dependencies: { packageInfo: updatePackageRoot ? await readCliPackageInfo(updatePackageRoot) : packageInfo,
|
|
846
851
|
logger: message => diagnostics.write(`update: ${message}`) },
|
|
@@ -1055,7 +1060,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
1055
1060
|
return;
|
|
1056
1061
|
}
|
|
1057
1062
|
if(command.action==='cancel_detach_update') {
|
|
1058
|
-
try {updateRuntime.validate(command,coordinator.snapshot(),updater?.snapshot?.());coordinator.cancel();emit({type:'result',id:command.id,ok:true});}
|
|
1063
|
+
try {updateRuntime.validate(command,coordinator.snapshot(),updater?.snapshot?.(),{accessReady:desktopAccessReady(currentAccess)});coordinator.cancel();emit({type:'result',id:command.id,ok:true});}
|
|
1059
1064
|
catch {emit({type:'result',id:command.id,ok:false,message:'This update operation changed. Refresh its status.'});}
|
|
1060
1065
|
return;
|
|
1061
1066
|
}
|
|
@@ -1124,7 +1129,7 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
1124
1129
|
const access = await accessSnapshot();
|
|
1125
1130
|
const commandScope = accessScope(access);
|
|
1126
1131
|
if (!desktopActionAllowed(access, command)) throw Object.assign(new Error('Desktop access is required.'), { code: 'desktop_access_required' });
|
|
1127
|
-
const recoveryControl = ['provider_disconnect', 'session_unmonitor'].includes(action) ||
|
|
1132
|
+
const recoveryControl = ['provider_disconnect', 'session_unmonitor'].includes(action) || action === 'auto_update_set_enabled' && command.enabled === false ||
|
|
1128
1133
|
['session_set_auto_continue', 'session_set_email_reports', 'team_thread_share'].includes(action) && command.enabled === false;
|
|
1129
1134
|
if (['detach_update', 'update_reconnect'].includes(action)) requireDesktopAccess(access);
|
|
1130
1135
|
else if (!recoveryControl && desktopAccessReady(access) && !desktopActionAllowed({ state: 'unavailable' }, command)) await authorizeWork();
|
|
@@ -1278,6 +1283,16 @@ export async function startDesktopHost({ input = process.stdin, output = process
|
|
|
1278
1283
|
if (!result.opened) throw new Error('Browser unavailable.');
|
|
1279
1284
|
} catch { throw Object.assign(new Error('Browser unavailable.'), { code: 'desktop_browser_unavailable' }); }
|
|
1280
1285
|
}
|
|
1286
|
+
} else if (action === 'auto_update_set_enabled') {
|
|
1287
|
+
if (typeof command.enabled !== 'boolean' || typeof updater.setAutomaticUpdates !== 'function') throw new Error('Automatic updates are unavailable.');
|
|
1288
|
+
const previous = updater.snapshot().auto_update_enabled === true;
|
|
1289
|
+
updater.setAutomaticUpdates(command.enabled);
|
|
1290
|
+
try { writeDesktopAutoUpdate(root, command.enabled); }
|
|
1291
|
+
catch (error) { updater.setAutomaticUpdates(previous); throw error; }
|
|
1292
|
+
emit({ type: 'result', id: command.id, ok: true });
|
|
1293
|
+
await publish();
|
|
1294
|
+
if (command.enabled) setImmediate(() => { if (!closed && !relay) void updater.checkNow().catch(() => {}); });
|
|
1295
|
+
return;
|
|
1281
1296
|
} else if (action === "check_update") await updater.checkNow();
|
|
1282
1297
|
else if (action === "update") {
|
|
1283
1298
|
const status = await agent.getDesktopStatus();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readState, atomicSave } from './update-detachment/state-store.js';
|
|
3
|
+
|
|
4
|
+
export function readDesktopAutoUpdate(root) {
|
|
5
|
+
try {
|
|
6
|
+
const settings = readState(join(root, 'desktop-update-settings.json'));
|
|
7
|
+
return settings === null ? true : settings.enabled === true;
|
|
8
|
+
}
|
|
9
|
+
catch { return false; }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function writeDesktopAutoUpdate(root, enabled) {
|
|
13
|
+
if (typeof enabled !== 'boolean') throw new Error('Choose whether automatic updates are enabled.');
|
|
14
|
+
atomicSave(join(root, 'desktop-update-settings.json'), { schemaVersion: 1, enabled });
|
|
15
|
+
}
|
|
@@ -259,18 +259,22 @@ export function createUpdateRuntime({ root, origin, readContext, listRecords, ob
|
|
|
259
259
|
};
|
|
260
260
|
return {bind,boundary,reconcile,store,gate:adapters.gate,
|
|
261
261
|
async initialize() {if(!transaction)write({connectionState:readDispatchGate(root).connection,phase:'startup',updateState:'pending'});},
|
|
262
|
-
snapshot(base,update) {
|
|
262
|
+
snapshot(base,update,{accessReady=true}={}) {
|
|
263
263
|
if(ownerTransferred)transaction=readState(file);
|
|
264
264
|
const action=base.state==='connected' && update?.state==='waiting_for_idle'?'detach_update':base.state==='detaching'?'cancel_detach_update':!base.busy && ['detached','recovery_required'].includes(base.state)?'update_reconnect':null;
|
|
265
265
|
const frame={...base,schemaVersion:1,connectionState:base.state,
|
|
266
|
-
updateState:transaction?.updateState || 'pending',tasks:rows(),actions:action?[action]:[]};
|
|
266
|
+
updateState:transaction?.updateState || 'pending',updaterState:update?.state || 'unknown',tasks:rows(),actions:action?[action]:[]};
|
|
267
|
+
// Revision the exact frame the native client sees. Filtering after the
|
|
268
|
+
// fingerprint makes an access change look like conflicting data at the
|
|
269
|
+
// same revision, which correctly fences all native update actions.
|
|
270
|
+
if(!accessReady) {frame.tasks=[];frame.actions=frame.actions.filter(action=>action==='cancel_detach_update');}
|
|
267
271
|
const fingerprint=digest(frame);
|
|
268
272
|
if(!ownerTransferred && transaction && transaction.presentation!==fingerprint)write({presentation:fingerprint});
|
|
269
273
|
presented={...frame,operationId:transaction?.operationId,revision:transaction?.revision || 0};
|
|
270
274
|
return presented;
|
|
271
275
|
},
|
|
272
|
-
validate(command,base,update) {
|
|
273
|
-
const frame=this.snapshot(base,update);
|
|
276
|
+
validate(command,base,update,options) {
|
|
277
|
+
const frame=this.snapshot(base,update,options);
|
|
274
278
|
if(command.operationId!==frame.operationId || command.revision!==frame.revision || !frame.actions.includes(command.action) || transaction?.claimedCommands?.includes(command.id))throw failure('stale_operation');
|
|
275
279
|
write({claimedCommands:[...(transaction?.claimedCommands || []).slice(-255),command.id]});
|
|
276
280
|
},
|