|
|
@@ -29,6 +29,7 @@ const skillSyncMocks = {
|
|
|
staged: [],
|
|
|
adopted: [],
|
|
|
customized: [],
|
|
|
+ stagedThisSync: [],
|
|
|
})),
|
|
|
};
|
|
|
|
|
|
@@ -121,6 +122,10 @@ describe('auto-update-checker/index', () => {
|
|
|
checkerMocks.getLatestVersion.mockImplementation(async () => null);
|
|
|
checkerMocks.getLocalDevVersion.mockReset();
|
|
|
checkerMocks.getLocalDevVersion.mockImplementation(() => null);
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockReset();
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockImplementation(
|
|
|
+ () => null,
|
|
|
+ );
|
|
|
|
|
|
cacheMocks.preparePackageUpdate.mockReset();
|
|
|
cacheMocks.preparePackageUpdate.mockImplementation(() => '/tmp/opencode');
|
|
|
@@ -147,6 +152,7 @@ describe('auto-update-checker/index', () => {
|
|
|
staged: [],
|
|
|
adopted: [],
|
|
|
customized: [],
|
|
|
+ stagedThisSync: [],
|
|
|
}));
|
|
|
|
|
|
companionUpdaterMocks.ensureCompanionVersion.mockReset();
|
|
|
@@ -236,13 +242,102 @@ describe('auto-update-checker/index', () => {
|
|
|
expect(showToast).toHaveBeenCalledWith({
|
|
|
body: {
|
|
|
title: 'OMO-Slim Updated!',
|
|
|
- message: 'v0.9.1 → v0.9.11\nRestart OpenCode to apply.',
|
|
|
+ message:
|
|
|
+ 'v0.9.1 → v0.9.11\nRestart OpenCode to apply the plugin update.',
|
|
|
variant: 'success',
|
|
|
duration: 8000,
|
|
|
},
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ test('shows a manual-review toast for newly staged startup skills when up to date', async () => {
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockImplementation(
|
|
|
+ () => '/tmp/opencode/package.json',
|
|
|
+ );
|
|
|
+ checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
+ pinnedVersion: null,
|
|
|
+ isPinned: false,
|
|
|
+ }));
|
|
|
+ checkerMocks.getCachedVersion.mockImplementation(() => '0.9.11');
|
|
|
+ checkerMocks.getLatestCompatibleVersion.mockImplementation(async () => ({
|
|
|
+ latestVersion: '0.9.11',
|
|
|
+ latestMajorVersion: null,
|
|
|
+ blockedByMajor: false,
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementation(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: ['reflect'],
|
|
|
+ adopted: [],
|
|
|
+ customized: ['reflect'],
|
|
|
+ stagedThisSync: ['reflect'],
|
|
|
+ }));
|
|
|
+
|
|
|
+ const { createAutoUpdateCheckerHook } = await import(
|
|
|
+ `./index?test=${importCounter++}`
|
|
|
+ );
|
|
|
+ const { ctx, showToast } = createCtx();
|
|
|
+
|
|
|
+ createAutoUpdateCheckerHook(ctx as never).event({
|
|
|
+ event: { type: 'session.created', properties: {} },
|
|
|
+ });
|
|
|
+ await waitForCalls(showToast);
|
|
|
+
|
|
|
+ expect(showToast).toHaveBeenCalledTimes(1);
|
|
|
+ expect(showToast).toHaveBeenCalledWith({
|
|
|
+ body: {
|
|
|
+ title: 'Skill updates need review',
|
|
|
+ message: 'Manual review required: reflect',
|
|
|
+ variant: 'info',
|
|
|
+ duration: 8000,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ test('shows a manual-review toast when a version-pinned update is staged at startup', async () => {
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockImplementation(
|
|
|
+ () => '/tmp/opencode/package.json',
|
|
|
+ );
|
|
|
+ checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
+ pinnedVersion: '0.9.1',
|
|
|
+ isPinned: true,
|
|
|
+ }));
|
|
|
+ checkerMocks.getLatestCompatibleVersion.mockImplementation(async () => ({
|
|
|
+ latestVersion: '0.9.11',
|
|
|
+ latestMajorVersion: null,
|
|
|
+ blockedByMajor: false,
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementation(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: ['reflect'],
|
|
|
+ adopted: [],
|
|
|
+ customized: ['reflect'],
|
|
|
+ stagedThisSync: ['reflect'],
|
|
|
+ }));
|
|
|
+
|
|
|
+ const { createAutoUpdateCheckerHook } = await import(
|
|
|
+ `./index?test=${importCounter++}`
|
|
|
+ );
|
|
|
+ const { ctx, showToast } = createCtx();
|
|
|
+
|
|
|
+ createAutoUpdateCheckerHook(ctx as never).event({
|
|
|
+ event: { type: 'session.created', properties: {} },
|
|
|
+ });
|
|
|
+ await waitForCalls(showToast, 2);
|
|
|
+
|
|
|
+ expect(showToast).toHaveBeenCalledWith({
|
|
|
+ body: {
|
|
|
+ title: 'Skill updates need review',
|
|
|
+ message: 'Manual review required: reflect',
|
|
|
+ variant: 'info',
|
|
|
+ duration: 8000,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
test('includes newly installed bundled skills in success toast', async () => {
|
|
|
checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
pinnedVersion: null,
|
|
|
@@ -261,6 +356,7 @@ describe('auto-update-checker/index', () => {
|
|
|
staged: [],
|
|
|
adopted: [],
|
|
|
customized: [],
|
|
|
+ stagedThisSync: [],
|
|
|
}));
|
|
|
|
|
|
const { createAutoUpdateCheckerHook } = await import(
|
|
|
@@ -276,14 +372,14 @@ describe('auto-update-checker/index', () => {
|
|
|
body: {
|
|
|
title: 'OMO-Slim Updated!',
|
|
|
message:
|
|
|
- 'v0.9.1 → v0.9.11\nAdded bundled skills: reflect, worktrees\nRestart OpenCode to apply.',
|
|
|
+ 'v0.9.1 → v0.9.11\nAdded bundled skills: reflect, worktrees\nRestart OpenCode to apply the plugin update.',
|
|
|
variant: 'success',
|
|
|
duration: 8000,
|
|
|
},
|
|
|
});
|
|
|
});
|
|
|
|
|
|
- test('includes staged and customized skills in success toast', async () => {
|
|
|
+ test('reports only new skill transitions in success toast without duplication', async () => {
|
|
|
checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
pinnedVersion: null,
|
|
|
isPinned: false,
|
|
|
@@ -300,7 +396,8 @@ describe('auto-update-checker/index', () => {
|
|
|
failed: [],
|
|
|
staged: ['worktrees'],
|
|
|
adopted: [],
|
|
|
- customized: ['my-custom-skill'],
|
|
|
+ customized: ['worktrees', 'my-custom-skill'],
|
|
|
+ stagedThisSync: ['worktrees'],
|
|
|
}));
|
|
|
|
|
|
const { createAutoUpdateCheckerHook } = await import(
|
|
|
@@ -316,7 +413,115 @@ describe('auto-update-checker/index', () => {
|
|
|
body: {
|
|
|
title: 'OMO-Slim Updated!',
|
|
|
message:
|
|
|
- 'v0.9.1 → v0.9.11\nAdded bundled skills: reflect\nStaged skill updates: worktrees\nCustomized skills: my-custom-skill\nRestart OpenCode to apply.',
|
|
|
+ 'v0.9.1 → v0.9.11\nAdded bundled skills: reflect\nStaged skill updates require manual review: worktrees\nRestart OpenCode to apply the plugin update.',
|
|
|
+ variant: 'success',
|
|
|
+ duration: 8000,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ test('retains staged transitions from startup reconciliation for the update toast', async () => {
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockImplementation(
|
|
|
+ () => '/tmp/opencode/package.json',
|
|
|
+ );
|
|
|
+ checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
+ pinnedVersion: null,
|
|
|
+ isPinned: false,
|
|
|
+ }));
|
|
|
+ checkerMocks.getCachedVersion.mockImplementation(() => '0.9.1');
|
|
|
+ checkerMocks.getLatestCompatibleVersion.mockImplementation(async () => ({
|
|
|
+ latestVersion: '0.9.11',
|
|
|
+ latestMajorVersion: null,
|
|
|
+ blockedByMajor: false,
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementationOnce(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: ['reflect'],
|
|
|
+ adopted: [],
|
|
|
+ customized: ['reflect'],
|
|
|
+ stagedThisSync: ['reflect'],
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementationOnce(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: [],
|
|
|
+ adopted: [],
|
|
|
+ customized: ['reflect'],
|
|
|
+ stagedThisSync: [],
|
|
|
+ }));
|
|
|
+
|
|
|
+ const { createAutoUpdateCheckerHook } = await import(
|
|
|
+ `./index?test=${importCounter++}`
|
|
|
+ );
|
|
|
+ const { ctx, showToast } = createCtx();
|
|
|
+
|
|
|
+ createAutoUpdateCheckerHook(ctx as never).event({
|
|
|
+ event: { type: 'session.created', properties: {} },
|
|
|
+ });
|
|
|
+ await waitForCalls(showToast);
|
|
|
+
|
|
|
+ expect(showToast).toHaveBeenCalledWith({
|
|
|
+ body: {
|
|
|
+ title: 'OMO-Slim Updated!',
|
|
|
+ message:
|
|
|
+ 'v0.9.1 → v0.9.11\nStaged skill updates require manual review: reflect\nRestart OpenCode to apply the plugin update.',
|
|
|
+ variant: 'success',
|
|
|
+ duration: 8000,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ test('removes startup staged transitions adopted by post-install sync', async () => {
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockImplementation(
|
|
|
+ () => '/tmp/opencode/package.json',
|
|
|
+ );
|
|
|
+ checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
+ pinnedVersion: null,
|
|
|
+ isPinned: false,
|
|
|
+ }));
|
|
|
+ checkerMocks.getCachedVersion.mockImplementation(() => '0.9.1');
|
|
|
+ checkerMocks.getLatestCompatibleVersion.mockImplementation(async () => ({
|
|
|
+ latestVersion: '0.9.11',
|
|
|
+ latestMajorVersion: null,
|
|
|
+ blockedByMajor: false,
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementationOnce(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: ['reflect'],
|
|
|
+ adopted: [],
|
|
|
+ customized: ['reflect'],
|
|
|
+ stagedThisSync: ['reflect'],
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementationOnce(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: [],
|
|
|
+ adopted: ['reflect'],
|
|
|
+ customized: [],
|
|
|
+ stagedThisSync: ['reflect'],
|
|
|
+ }));
|
|
|
+
|
|
|
+ const { createAutoUpdateCheckerHook } = await import(
|
|
|
+ `./index?test=${importCounter++}`
|
|
|
+ );
|
|
|
+ const { ctx, showToast } = createCtx();
|
|
|
+
|
|
|
+ createAutoUpdateCheckerHook(ctx as never).event({
|
|
|
+ event: { type: 'session.created', properties: {} },
|
|
|
+ });
|
|
|
+ await waitForCalls(showToast);
|
|
|
+
|
|
|
+ expect(showToast).toHaveBeenCalledWith({
|
|
|
+ body: {
|
|
|
+ title: 'OMO-Slim Updated!',
|
|
|
+ message:
|
|
|
+ 'v0.9.1 → v0.9.11\nRestart OpenCode to apply the plugin update.',
|
|
|
variant: 'success',
|
|
|
duration: 8000,
|
|
|
},
|
|
|
@@ -375,7 +580,7 @@ describe('auto-update-checker/index', () => {
|
|
|
body: {
|
|
|
title: 'OMO-Slim Updated!',
|
|
|
message:
|
|
|
- 'v0.9.1 → v0.9.11\nCompanion updated.\nRestart OpenCode to apply.',
|
|
|
+ 'v0.9.1 → v0.9.11\nCompanion updated.\nRestart OpenCode to apply the plugin update.',
|
|
|
variant: 'success',
|
|
|
duration: 8000,
|
|
|
},
|
|
|
@@ -416,7 +621,7 @@ describe('auto-update-checker/index', () => {
|
|
|
body: {
|
|
|
title: 'OMO-Slim Updated!',
|
|
|
message:
|
|
|
- 'v0.9.1 → v0.9.11\nCompanion update will retry on restart.\nRestart OpenCode to apply.',
|
|
|
+ 'v0.9.1 → v0.9.11\nCompanion update will retry on restart.\nRestart OpenCode to apply the plugin update.',
|
|
|
variant: 'success',
|
|
|
duration: 8000,
|
|
|
},
|
|
|
@@ -441,6 +646,7 @@ describe('auto-update-checker/index', () => {
|
|
|
staged: [],
|
|
|
adopted: [],
|
|
|
customized: [],
|
|
|
+ stagedThisSync: [],
|
|
|
}));
|
|
|
|
|
|
const { createAutoUpdateCheckerHook } = await import(
|
|
|
@@ -455,7 +661,8 @@ describe('auto-update-checker/index', () => {
|
|
|
expect(showToast).toHaveBeenCalledWith({
|
|
|
body: {
|
|
|
title: 'OMO-Slim Updated!',
|
|
|
- message: 'v0.9.1 → v0.9.11\nRestart OpenCode to apply.',
|
|
|
+ message:
|
|
|
+ 'v0.9.1 → v0.9.11\nRestart OpenCode to apply the plugin update.',
|
|
|
variant: 'success',
|
|
|
duration: 8000,
|
|
|
},
|
|
|
@@ -582,6 +789,58 @@ describe('auto-update-checker/index', () => {
|
|
|
});
|
|
|
});
|
|
|
|
|
|
+ test('shows a manual-review toast when installation fails after startup staging', async () => {
|
|
|
+ checkerMocks.getCurrentRuntimePackageJsonPath.mockImplementation(
|
|
|
+ () => '/tmp/opencode/package.json',
|
|
|
+ );
|
|
|
+ checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
+ pinnedVersion: null,
|
|
|
+ isPinned: false,
|
|
|
+ }));
|
|
|
+ checkerMocks.getCachedVersion.mockImplementation(() => '0.9.1');
|
|
|
+ checkerMocks.getLatestCompatibleVersion.mockImplementation(async () => ({
|
|
|
+ latestVersion: '0.9.11',
|
|
|
+ latestMajorVersion: null,
|
|
|
+ blockedByMajor: false,
|
|
|
+ }));
|
|
|
+ skillSyncMocks.syncBundledSkillsFromPackage.mockImplementation(() => ({
|
|
|
+ installed: [],
|
|
|
+ skippedExisting: [],
|
|
|
+ failed: [],
|
|
|
+ staged: ['reflect'],
|
|
|
+ adopted: [],
|
|
|
+ customized: ['reflect'],
|
|
|
+ stagedThisSync: ['reflect'],
|
|
|
+ }));
|
|
|
+ crossSpawnMock.mockImplementation(() => ({
|
|
|
+ exited: Promise.resolve(1),
|
|
|
+ exitCode: 1,
|
|
|
+ kill: mock(() => true),
|
|
|
+ stdout: () => Promise.resolve(''),
|
|
|
+ stderr: () => Promise.resolve(''),
|
|
|
+ proc: {} as never,
|
|
|
+ }));
|
|
|
+
|
|
|
+ const { createAutoUpdateCheckerHook } = await import(
|
|
|
+ `./index?test=${importCounter++}`
|
|
|
+ );
|
|
|
+ const { ctx, showToast } = createCtx();
|
|
|
+
|
|
|
+ createAutoUpdateCheckerHook(ctx as never).event({
|
|
|
+ event: { type: 'session.created', properties: {} },
|
|
|
+ });
|
|
|
+ await waitForCalls(showToast, 2);
|
|
|
+
|
|
|
+ expect(showToast).toHaveBeenCalledWith({
|
|
|
+ body: {
|
|
|
+ title: 'Skill updates need review',
|
|
|
+ message: 'Manual review required: reflect',
|
|
|
+ variant: 'info',
|
|
|
+ duration: 8000,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
test('does not auto-update across major versions', async () => {
|
|
|
checkerMocks.findPluginEntry.mockImplementation(() => ({
|
|
|
pinnedVersion: null,
|
|
|
@@ -732,6 +991,7 @@ describe('auto-update-checker/index', () => {
|
|
|
staged: ['reflect'],
|
|
|
adopted: [],
|
|
|
customized: ['my-custom-skill'],
|
|
|
+ stagedThisSync: [],
|
|
|
}));
|
|
|
|
|
|
const { createAutoUpdateCheckerHook } = await import(
|