feat(agents): Enhance agent update confirmation and status handling
This commit is contained in:
@@ -108,7 +108,7 @@ async def _ensure_gateway_session(
|
|||||||
|
|
||||||
def _with_computed_status(agent: Agent) -> Agent:
|
def _with_computed_status(agent: Agent) -> Agent:
|
||||||
now = datetime.utcnow()
|
now = datetime.utcnow()
|
||||||
if agent.status == "deleting":
|
if agent.status in {"deleting", "updating"}:
|
||||||
return agent
|
return agent
|
||||||
if agent.last_seen_at is None:
|
if agent.last_seen_at is None:
|
||||||
agent.status = "provisioning"
|
agent.status = "provisioning"
|
||||||
@@ -283,6 +283,7 @@ async def update_agent(
|
|||||||
agent.provision_confirm_token_hash = hash_agent_token(provision_token)
|
agent.provision_confirm_token_hash = hash_agent_token(provision_token)
|
||||||
agent.provision_requested_at = datetime.utcnow()
|
agent.provision_requested_at = datetime.utcnow()
|
||||||
agent.provision_action = "update"
|
agent.provision_action = "update"
|
||||||
|
agent.status = "updating"
|
||||||
session.add(agent)
|
session.add(agent)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(agent)
|
session.refresh(agent)
|
||||||
@@ -560,6 +561,8 @@ def confirm_provision_agent(
|
|||||||
agent.provision_confirm_token_hash = None
|
agent.provision_confirm_token_hash = None
|
||||||
agent.provision_requested_at = None
|
agent.provision_requested_at = None
|
||||||
agent.provision_action = None
|
agent.provision_action = None
|
||||||
|
if action == "update":
|
||||||
|
agent.status = "online"
|
||||||
agent.updated_at = datetime.utcnow()
|
agent.updated_at = datetime.utcnow()
|
||||||
session.add(agent)
|
session.add(agent)
|
||||||
record_activity(
|
record_activity(
|
||||||
|
|||||||
@@ -210,9 +210,10 @@ def build_update_message(
|
|||||||
"```\n"
|
"```\n"
|
||||||
"Note: if any agents.list entry defines heartbeat, only those agents "
|
"Note: if any agents.list entry defines heartbeat, only those agents "
|
||||||
"run heartbeats.\n"
|
"run heartbeats.\n"
|
||||||
"7) After the update completes, confirm by calling:\n"
|
"7) After the update completes (and only after files are written), confirm by calling:\n"
|
||||||
f" POST {context['base_url']}/api/v1/agents/{context['agent_id']}/provision/confirm\n"
|
f" POST {context['base_url']}/api/v1/agents/{context['agent_id']}/provision/confirm\n"
|
||||||
f" Body: {{\"token\": \"{confirm_token}\", \"action\": \"update\"}}\n\n"
|
f" Body: {{\"token\": \"{confirm_token}\", \"action\": \"update\"}}\n"
|
||||||
|
" Mission Control will send the hello message only after this confirmation.\n\n"
|
||||||
"Files:" + file_blocks
|
"Files:" + file_blocks
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,18 @@ type ActivityEvent = {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimestamp = (value: string) => {
|
const parseTimestamp = (value?: string | null) => {
|
||||||
const date = new Date(value);
|
if (!value) return null;
|
||||||
if (Number.isNaN(date.getTime())) return "—";
|
const hasTz = /[zZ]|[+-]\d\d:\d\d$/.test(value);
|
||||||
|
const normalized = hasTz ? value : `${value}Z`;
|
||||||
|
const date = new Date(normalized);
|
||||||
|
if (Number.isNaN(date.getTime())) return null;
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTimestamp = (value?: string | null) => {
|
||||||
|
const date = parseTimestamp(value);
|
||||||
|
if (!date) return "—";
|
||||||
return date.toLocaleString(undefined, {
|
return date.toLocaleString(undefined, {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
@@ -59,9 +68,9 @@ const formatTimestamp = (value: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatRelative = (value: string) => {
|
const formatRelative = (value?: string | null) => {
|
||||||
const date = new Date(value);
|
const date = parseTimestamp(value);
|
||||||
if (Number.isNaN(date.getTime())) return "—";
|
if (!date) return "—";
|
||||||
const diff = Date.now() - date.getTime();
|
const diff = Date.now() - date.getTime();
|
||||||
const minutes = Math.round(diff / 60000);
|
const minutes = Math.round(diff / 60000);
|
||||||
if (minutes < 1) return "Just now";
|
if (minutes < 1) return "Just now";
|
||||||
|
|||||||
@@ -63,9 +63,18 @@ type GatewayStatus = {
|
|||||||
error?: string;
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatTimestamp = (value: string) => {
|
const parseTimestamp = (value?: string | null) => {
|
||||||
const date = new Date(value);
|
if (!value) return null;
|
||||||
if (Number.isNaN(date.getTime())) return "—";
|
const hasTz = /[zZ]|[+-]\d\d:\d\d$/.test(value);
|
||||||
|
const normalized = hasTz ? value : `${value}Z`;
|
||||||
|
const date = new Date(normalized);
|
||||||
|
if (Number.isNaN(date.getTime())) return null;
|
||||||
|
return date;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTimestamp = (value?: string | null) => {
|
||||||
|
const date = parseTimestamp(value);
|
||||||
|
if (!date) return "—";
|
||||||
return date.toLocaleString(undefined, {
|
return date.toLocaleString(undefined, {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
@@ -74,9 +83,9 @@ const formatTimestamp = (value: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatRelative = (value: string) => {
|
const formatRelative = (value?: string | null) => {
|
||||||
const date = new Date(value);
|
const date = parseTimestamp(value);
|
||||||
if (Number.isNaN(date.getTime())) return "—";
|
if (!date) return "—";
|
||||||
const diff = Date.now() - date.getTime();
|
const diff = Date.now() - date.getTime();
|
||||||
const minutes = Math.round(diff / 60000);
|
const minutes = Math.round(diff / 60000);
|
||||||
if (minutes < 1) return "Just now";
|
if (minutes < 1) return "Just now";
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const STATUS_STYLES: Record<
|
|||||||
provisioning: "warning",
|
provisioning: "warning",
|
||||||
offline: "outline",
|
offline: "outline",
|
||||||
deleting: "danger",
|
deleting: "danger",
|
||||||
|
updating: "accent",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function StatusPill({ status }: { status: string }) {
|
export function StatusPill({ status }: { status: string }) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
On startup:
|
On startup:
|
||||||
1) Verify API reachability (GET {{ base_url }}/api/v1/gateway/status).
|
1) Verify API reachability (GET {{ base_url }}/api/v1/gateway/status).
|
||||||
|
- A 401 Unauthorized response is acceptable here for agents (auth-protected endpoint).
|
||||||
2) Connect to Mission Control once by sending a heartbeat check-in.
|
2) Connect to Mission Control once by sending a heartbeat check-in.
|
||||||
2a) Use task comments for updates; do not send task updates to chat/web.
|
2a) Use task comments for updates; do not send task updates to chat/web.
|
||||||
3) If you send a boot message, end with NO_REPLY.
|
3) If you send a boot message, end with NO_REPLY.
|
||||||
|
|||||||
Reference in New Issue
Block a user