Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2354-reach-ready-only.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A schedule row no longer names a connector whose connection failed, or one still connecting, as carried by its unattended run: the host unmounts a failed connection before it writes `error` and a `connecting` one has no tools yet, so only a `ready` connector appears in the row's `Runs with …` sentence, while the Connectors page keeps the switch that reconnects it ([#2354](https://github.com/ThinkFlowLab/FlyBot/issues/2354)).
2 changes: 1 addition & 1 deletion core/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -5573,7 +5573,7 @@ function reachText(task, roster) {
if (!task?.botId) return "";
const bot = roster?.byId?.[task.botId] ?? (roster?.bots ?? []).find((entry) => entry.id === task.botId);
if (bot === void 0) return "";
const names = connectedConnectors(roster).filter((connector) => isOn(bot, connector.id)).map((connector) => connector.name);
const names = connectedConnectors(roster).filter((connector) => connector.state === "ready" && isOn(bot, connector.id)).map((connector) => connector.name);
return names.length === 0 ? t("Runs with no connectors") : t("Runs with {names}", { names: names.join(", ") });
}
var lockedWindow = (roster) => typeof roster?.readOnly === "string" && roster.readOnly.trim() !== "";
Expand Down
9 changes: 7 additions & 2 deletions core/src/client/schedules.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,17 @@ export function lastRunText(run) {
// stored with the task, so turning a connector on today changes what a task written weeks ago will
// use. A room task (no `botId`) has no single Bot's switch, and a Bot the roster no longer carries
// says nothing rather than guessing. `connectedConnectors` drops a sender and a webhook-only
// channel, which have neither tools nor a way in; `isOn` keeps only what this Bot may use.
// channel, which have neither tools nor a way in; `isOn` keeps only what this Bot may use. Only a
// `ready` connection gives the run tools (#2354): the host unmounts a failed connection before it
// writes `error`, and a `connecting` one has none yet — `connectedConnectors` still names both so
// the Connectors page can draw the switch that reconnects them.
export function reachText(task, roster) {
if (!task?.botId) return ''
const bot = roster?.byId?.[task.botId] ?? (roster?.bots ?? []).find(entry => entry.id === task.botId)
if (bot === undefined) return ''
const names = connectedConnectors(roster).filter(connector => isOn(bot, connector.id)).map(connector => connector.name)
const names = connectedConnectors(roster)
.filter(connector => connector.state === 'ready' && isOn(bot, connector.id))
.map(connector => connector.name)
return names.length === 0 ? t('Runs with no connectors') : t('Runs with {names}', { names: names.join(', ') })
}

Expand Down
33 changes: 30 additions & 3 deletions core/test/schedules-client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,9 +846,13 @@ test('P-482: a locked Bot card with tasks keeps the heading and the rows', () =>
// with the task, so its row says which connectors an unattended run would carry. A room task has
// no single Bot's switch, so it says nothing: drawing one would state a falsehood.
const CONNECTORS = [
{ id: 'github', name: 'GitHub', state: 'connected' },
{ id: 'composio', name: 'Composio', state: 'connected' },
{ id: 'github', name: 'GitHub', state: 'ready' },
{ id: 'composio', name: 'Composio', state: 'ready' },
]
// The host writes exactly four connector states — off, connecting, ready, error — and the row
// names only what can give the run tools (#2354). The fixture below used to say 'connected', a
// value the host never writes, which is why any predicate let it through.
const ERRORED = { id: 'composio', name: 'Composio', state: 'error' }
const reachRoster = bot => ({ bots: [bot], rooms: [], byId: { [bot.id]: bot }, roomsById: {}, connectors: CONNECTORS })
const reachLines = roster => {
seeded.push({ available: true, tasks: [ACTIVE] }, null)
Expand All @@ -872,7 +876,7 @@ test('#2323 c: every connected connector off reads as no connectors', () => {
})

test('#2323 d: a sender connector never appears in the sentence', () => {
const roster = { ...reachRoster({ id: 'b1', name: 'B' }), connectors: [...CONNECTORS, { id: 'feishu', name: 'Feishu', state: 'connected', kind: 'sender' }] }
const roster = { ...reachRoster({ id: 'b1', name: 'B' }), connectors: [...CONNECTORS, { id: 'feishu', name: 'Feishu', state: 'ready', kind: 'sender' }] }
const lines = reachLines(roster)
assert.equal(reachOf(lines), 'Runs with GitHub, Composio')
assert.ok(!lines.join('|').includes('Feishu'), 'a sender has neither tools nor a way in')
Expand All @@ -885,3 +889,26 @@ test('#2323 e: a room task says nothing about reach', () => {
const lines = text(render(RoomSchedules({ room: { id: 'room-1', name: 'Team room', origin: 'group' }, actions: spy() })))
assert.ok(!lines.some(line => line.startsWith('Runs with')), lines.join('|'))
})

// #2354: reconcile() unmounts a connection before it writes `error`
// (core/src/host/connectors.js:750-757), so a failed connector's tools are gone and its row must
// not promise them; `connecting` has no tools yet either. The Connectors page still draws a switch
// for both, which is how the owner reconnects, so `connectedConnectors` keeps its own predicate.
test('#2354 a: a failed connection is not named as carried, a ready one still is', () => {
const roster = { ...reachRoster({ id: 'b1', name: 'B' }), connectors: [CONNECTORS[0], ERRORED] }
const lines = reachLines(roster)
assert.equal(reachOf(lines), 'Runs with GitHub')
assert.ok(!lines.join('|').includes('Composio'), 'a connector whose connection failed carries nothing')
})

test('#2354 b: nothing ready left reads as no connectors, not as the failed ones', () => {
const roster = { ...reachRoster({ id: 'b1', name: 'B' }), connectors: [{ id: 'github', name: 'GitHub', state: 'error' }, ERRORED] }
assert.equal(reachOf(reachLines(roster)), 'Runs with no connectors')
})

test('#2354 c: a connector still connecting is not named', () => {
const roster = { ...reachRoster({ id: 'b1', name: 'B' }), connectors: [CONNECTORS[0], { id: 'composio', name: 'Composio', state: 'connecting' }] }
const lines = reachLines(roster)
assert.equal(reachOf(lines), 'Runs with GitHub')
assert.ok(!lines.join('|').includes('Composio'), 'a connecting connector has no tools yet')
})
Loading
Loading